fork(1) download
  1. def find_descriptor1(instance, attrname):
  2. '''Find the descriptor handling a given attribute, if any.
  3.  
  4. If the attribute named attrname of the given instance is handled by a
  5. descriptor, this will return the descriptor object handling the attribute.
  6. Otherwise, it will return None.
  7. '''
  8. def hasspecialmethod(obj, name):
  9. return any(name in klass.__dict__ for klass in type(obj).__mro__)
  10. for klass in type(instance).__mro__:
  11. if attrname in klass.__dict__:
  12. descriptor = klass.__dict__[attrname]
  13. if not (hasspecialmethod(descriptor, '__get__') or
  14. hasspecialmethod(descriptor, '__set__') or
  15. hasspecialmethod(descriptor, '__delete__')):
  16. # Attribute isn't a descriptor
  17. return None
  18. if (attrname in instance.__dict__ and
  19. not hasspecialmethod(descriptor, '__set__') and
  20. not hasspecialmethod(descriptor, '__delete__')):
  21. # Would be handled by the descriptor, but the descriptor isn't
  22. # a data descriptor and the object has a dict entry overriding
  23. # it.
  24. return None
  25. return descriptor
  26. return None
  27.  
  28. def find_descriptor2(obj, name:str):
  29. """ Find the descriptor, if any, an attribute retrieval on an instance would go through
  30.  
  31. :param obj: instance object
  32. :param name: attribute which would be retrieved
  33. :returns: unbound descriptor ``__get__``, or ``None`` if no descriptor would be used
  34. """
  35. # lookup attribute in class hierarchy
  36. for base in type(obj).__mro__:
  37. base_dict = vars(base)
  38. if name in base_dict:
  39. # we look for descriptor interface on value's class, not value itself
  40. value_clazz = type(base_dict[name])
  41. descr = getattr(value_clazz, '__get__', None)
  42. if descr:
  43. # data descriptor?
  44. if (hasattr(value_clazz, '__set__') or hasattr(value_clazz, "__delete__")):
  45. return descr
  46. # instance variable can be used instead?
  47. if hasattr(obj, '__dict__') and name in vars(obj):
  48. return
  49. # non-data descriptor?
  50. # if None, it indicates a class variable
  51. return descr
  52.  
  53. class WeirdDescriptor:
  54. def __set_name__(self, owner, name):
  55. self.name = name
  56. def __set__(self, instance, value):
  57. print('setter invoked!')
  58. instance.__dict__[self.name] = value
  59.  
  60. class Foo:
  61. x = WeirdDescriptor()
  62.  
  63. x = Foo()
  64. x.x = 2
  65. print(x.x)
  66. print(find_descriptor1(x, 'x'))
  67. print(find_descriptor2(x, 'x'))
Success #stdin #stdout 0.03s 9620KB
stdin
Standard input is empty
stdout
setter invoked!
2
<__main__.WeirdDescriptor object at 0x15284288bd00>
None