New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Documentation incorrectly states how descriptors are invoked #75916
Comments
The descriptor howto states: "For example, obj.d looks up d in the dictionary of obj. If d defines the method __get__(), then d.__get__(obj) is invoked [...]" This is not true - the descriptor obtained from obj's dictionary is never invoked. If it was, the following two snippets would produce output: class Class:
pass
obj = Class()
obj.__dict__['d'] = property(lambda: print('called'))
_ = obj.d # nothing is printed.
class Obj:
@property
def d(self):
print('called')
_ = Obj.d # nothing is printed. |
You get what you should get: when you print obj.d, Obj.d, you will get: <property object at 0xDEADBEEF> which is exactly what you expect:
If you run print(Obj().d) you will get a TypeError: your lambda should read: lambda self: print('called') Properties should be added to the class not the instance, see https://stackoverflow.com/questions/1325673/how-to-add-property-to-a-class-dynamically and https://eev.ee/blog/2012/05/23/python-faq-descriptors/ |
I'm aware that descriptors have to exist on the class in order to work. The point is that the documentation states "If d defines the method __get__(), then d.__get__(obj) is invoked" (where d is obj.d), which is simply not true. |
If we take this class: class Obj:
@property
def d(self):
print('called') And we access Obj.d: _ = Obj.d According to the docs, the following should happen: # obj.d looks up d in the dictionary of obj
d = Obj.__dict__['d']
# If d defines the method __get__(),
if hasattr(d, '__get__'):
# then d.__get__(obj) is invoked
d.__get__(Obj) We know this doesn't happen because nothing is printed to stdout. |
"We know this doesn't happen because nothing is printed to stdout." Try running Obj().d, you will get output. Obj.d does not work because it is on a *class*, and so it runs, per the docs: 'Obj.__dict__['d'].__get__(None, Obj)' whereas you consider running it on an instance to get: b = Obj()
b.d
# equivalent to
type(b).__dict__['d'].__get__(b, type(b)) and you will get output twice. [Note, on python2 you will get an error, I think this is because your class does not inherit from object.] |
I do think though that "If d defines the method __get__(), then d.__get__(obj) is invoked according to the precedence rules listed below." seems to contain a mistake in that it should have d.__get__(obj, type(obj)) instead of d.__get__(obj) |
See also bpo-20751 |
The objtype argument is optional as shown in all of the examples. The call from object.__getattribute__() always passes in both parameters, even though only the first is required.
class A:
def __init__(self, x):
self.x = x
def m(self, y):
return self.x * y >>> a = A(10)
>>> a.m(5)
50
>>> vars(A)['m'].__get__(a)(5) # objtype is not required
50
>>> vars(A)['m'].__get__(a, A)(5) # objtype may be used
50
class Desc:
def __get__(self, *args):
return args
class B:
z = Desc()
>>> b = B()
>>> b.z
(<__main__.B object at 0x109156110>, <class '__main__.B'>) |
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields:
The text was updated successfully, but these errors were encountered: