This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: Documentation incorrectly states how descriptors are invoked
Type: Stage: resolved
Components: Documentation Versions: Python 3.11, Python 3.10, Python 3.9
process
Status: closed Resolution: not a bug
Dependencies: Superseder:
Assigned To: rhettinger Nosy List: Paul Pinterits, cryvate, docs@python, iritkatriel, rhettinger
Priority: normal Keywords:

Created on 2017-10-09 15:04 by Paul Pinterits, last changed 2022-04-11 14:58 by admin. This issue is now closed.

Messages (8)
msg303968 - (view) Author: Paul Pinterits (Paul Pinterits) Date: 2017-10-09 15:04
The [descriptor howto](https://docs.python.org/3/howto/descriptor.html#invoking-descriptors) 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.
msg303978 - (view) Author: Henk-Jaap Wagenaar (cryvate) * Date: 2017-10-09 15:50
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:
- in the first case, you assigned a property object to the dictionary at obj.__dict__, so that's what you get back when you run obj.d.
- you defined a property on a class called d, and you get it when you run Obj.d

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/
msg303984 - (view) Author: Paul Pinterits (Paul Pinterits) Date: 2017-10-09 16:47
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.
msg303986 - (view) Author: Paul Pinterits (Paul Pinterits) Date: 2017-10-09 16:55
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.
msg304021 - (view) Author: Henk-Jaap Wagenaar (cryvate) * Date: 2017-10-10 08:46
"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.]
msg304022 - (view) Author: Henk-Jaap Wagenaar (cryvate) * Date: 2017-10-10 08:59
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)
msg407603 - (view) Author: Irit Katriel (iritkatriel) * (Python committer) Date: 2021-12-03 21:16
See also Issue20751
msg407623 - (view) Author: Raymond Hettinger (rhettinger) * (Python committer) Date: 2021-12-04 01:44
> it should have
>
> d.__get__(obj, type(obj)) instead of d.__get__(obj)


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.
  
    ################################################################
    # Demonstration of __get__() being called with one or two params

    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


    ################################################################
    # Demonstration of object.__getattribute__ supplying both args

    class Desc:
        def __get__(self, *args):
            return args
      
    class B:
        z = Desc()
     
    >>> b = B()
    >>> b.z
    (<__main__.B object at 0x109156110>, <class '__main__.B'>)
History
Date User Action Args
2022-04-11 14:58:53adminsetgithub: 75916
2021-12-04 01:44:12rhettingersetstatus: open -> closed
resolution: not a bug
messages: + msg407623

stage: resolved
2021-12-03 21:46:26iritkatrielsetversions: + Python 3.9, Python 3.10, Python 3.11, - Python 2.7, Python 3.4, Python 3.5, Python 3.6, Python 3.7, Python 3.8
2021-12-03 21:44:28rhettingersetassignee: docs@python -> rhettinger
2021-12-03 21:16:31iritkatrielsetnosy: + iritkatriel, rhettinger
messages: + msg407603
2017-10-10 08:59:44cryvatesetmessages: + msg304022
2017-10-10 08:46:31cryvatesetmessages: + msg304021
2017-10-09 16:55:22Paul Pinteritssetmessages: + msg303986
2017-10-09 16:47:05Paul Pinteritssetmessages: + msg303984
2017-10-09 15:50:39cryvatesetnosy: + cryvate
messages: + msg303978
2017-10-09 15:04:44Paul Pinteritscreate