Skip to content
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

Incorrect description of descriptor invocation in Python Language Reference #67506

Closed
JustinEldridge mannequin opened this issue Jan 25, 2015 · 5 comments
Closed

Incorrect description of descriptor invocation in Python Language Reference #67506

JustinEldridge mannequin opened this issue Jan 25, 2015 · 5 comments
Labels
docs Documentation in the Doc dir type-feature A feature request or enhancement

Comments

@JustinEldridge
Copy link
Mannequin

JustinEldridge mannequin commented Jan 25, 2015

BPO 23317
Nosy @rhettinger, @bitdancer, @iritkatriel

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:

assignee = None
closed_at = <Date 2021-12-04.01:54:16.733>
created_at = <Date 2015-01-25.17:16:03.194>
labels = ['type-feature', 'docs']
title = 'Incorrect description of descriptor invocation in Python Language Reference'
updated_at = <Date 2021-12-04.01:54:16.732>
user = 'https://bugs.python.org/JustinEldridge'

bugs.python.org fields:

activity = <Date 2021-12-04.01:54:16.732>
actor = 'rhettinger'
assignee = 'docs@python'
closed = True
closed_date = <Date 2021-12-04.01:54:16.733>
closer = 'rhettinger'
components = ['Documentation']
creation = <Date 2015-01-25.17:16:03.194>
creator = 'Justin.Eldridge'
dependencies = []
files = []
hgrepos = []
issue_num = 23317
keywords = []
message_count = 5.0
messages = ['234676', '234684', '234763', '407605', '407624']
nosy_count = 5.0
nosy_names = ['rhettinger', 'r.david.murray', 'docs@python', 'Justin.Eldridge', 'iritkatriel']
pr_nums = []
priority = 'normal'
resolution = 'out of date'
stage = 'resolved'
status = 'closed'
superseder = None
type = 'enhancement'
url = 'https://bugs.python.org/issue23317'
versions = ['Python 2.7', 'Python 3.4', 'Python 3.5']

@JustinEldridge
Copy link
Mannequin Author

JustinEldridge mannequin commented Jan 25, 2015

The section titled "Invoking Descriptors" in the Python Language Reference 1
says:

Class Binding
    If binding to a new-style class, A.x is transformed into the call: 
    A.__dict__['x'].__get__(None, A).

This suggests that __get__ is looked up on the instance of x, when I believe
that it is actually looked up on the type. That is, it's my understanding that
A.x invokes:

type(A.__dict__['x']).__get__(A.__dict__['x'], None, Foo))

Here's some Python 3.4 code demonstrating this:

    class A:
        pass

    class Descriptor:
        def __get__(self, obj, cls):
            print("Getting!")
            
    A.x = Descriptor()

    def replacement_get(obj, cls):
        print("This is a replacement.")
        
    A.__dict__['x'].__get__ = replacement_get

Now, writing:

    >>> A.x
    Getting!

    >>> A.__dict__['x'].__get__(None, A)
    This is a replacement!

    >>> type(A.__dict__['x']).__get__(A.__dict__['x'], None, A)
    Getting!

The documentation makes a similar statement about instance binding that also
appears to be incorrect. This is the case in all versions of the document I
could find.

What I believe to be the actual behavior is implied by a later section in the
same document, which states that the implicit invocation of special methods is
only guaranteed to work correctly if the special method is defined on the type,
not the instance. This suggests that the statements in "Invoking Descriptors"
aren't quite correct, and while the true behavior is a little more verbose, I
think it would be worthwhile to update the documentation so as to avoid
confusion.

@JustinEldridge JustinEldridge mannequin added docs Documentation in the Doc dir type-feature A feature request or enhancement labels Jan 25, 2015
@bitdancer
Copy link
Member

I believe that you are correct: special methods are looked up on the type, and this is assumed implicitly in the Class Binding description. (This was not 100% true in python2, but it is in current python3.) But the Class Binding description is correct, since if the __get__ method is *not* defined on the type (of the descriptor), the descriptor instance itself is returned (so explicitly calling type in the "equivalent expression" would be wrong).

This is one of the most complex topics to describe in Python (I still don't have it solid in my head and I've been working with Python for years now). If we can come up with clearer wording that is good, but we've tried several times already to improve it :(

I don't know what you are referring to for the 'instance binding' that makes the same 'mistake', but I suspect it is also covered by the "special methods are looked up on the type" rule.

@JustinEldridge
Copy link
Mannequin Author

JustinEldridge mannequin commented Jan 26, 2015

Ah, I see how writing a description of this which is both concise and precise
would be difficult, especially for Python 2.

But the Class Binding description is correct, since if the __get__ method is
*not* defined on the type (of the descriptor), the descriptor instance itself
is returned (so explicitly calling type in the "equivalent expression" would be
wrong)

I see, but couldn't this also be held against the current "equivalent"? That
is, saying A.x is equivalent to A.__dict__['x'].__get__(None, A) is not stricly
true when __get__ isn't defined on type(x).

I think I see now why this is difficult to document cleanly, and the "Special
method lookup" section of the documentation does a good job of explaining this.
The issue isn't exclusive to descriptors. It affects, for example, the
documentation on rich comparison operators, which says that x == y invokes
x.__eq__(y), when this hasn't quite been true since old-style classes.

So saying x == y is equivalent to x.__eq__(y) isn't really correct, and saying
that it is equivalent to type(x).__eq__(x,y) isn't quite right either, as
implicit invocation may bypass the metaclass's __getattribute__. The latter,
however, seems "less wrong". Is there a reason that the former is preferred by
the documentation?

@iritkatriel
Copy link
Member

See also bpo-20751.

@rhettinger
Copy link
Contributor

I think we can leave this as-is. It does a reasonable job of communicating where the descriptor is found and the arguments used when it is called.

Marking this as out of date because later in the howto guide there is a precise pure python equivalent for the lookup and invocation steps.

@ezio-melotti ezio-melotti transferred this issue from another repository Apr 10, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
docs Documentation in the Doc dir type-feature A feature request or enhancement
Projects
None yet
Development

No branches or pull requests

3 participants