Eric Snow <[email protected]> added the comment:
First of all, thanks for asking about this. Everything is working as expected.
Let's look at why.
First, be sure the behavior of descriptors is clear: the descriptor protocol is
only triggered by "dotted access" on an object ("obj.attr"). So you should
expect it only where you see that syntax used.
Let's look at your examples now.
> FirstClass().descriptor = None
In this case there are 2 dotted accesses. The first one happens in __init__()
when the object is created. The second is the rest of the above line.
> class SecondClass(metaclass=SecondClassMeta):
> descriptor = None
>
> SecondClass.descriptor = None
In this case there is only one dotted access, in that last line. The object in
this case is SecondClass and its class is SecondClassMeta. Unlike with
FirstClass, the *class* in the second example (SecondClassMeta) does not have a
__init__() with the dotted access. Instead there is only the one dotted access
afterward. If SecondClassMeta had the same __init__() that FirstClass had then
you would have seen a second trigger of the descriptor.
It seems you expected assignments (name binding) in the class definition body
to be treated the same as dotted access. They are not. This is because when a
class definition body is evaluated, the class object does not exist yet. The
steps for class creation go like this:
1. figure out the metaclass (by default "type")
2. calls its __prepare__() method to get a namespace
3. execute the class body (like a function) with that namespace as the locals
4. create the class object, passing in that namespace
Python has worked this way since version 2.2 (PEP 252). See:
https://docs.python.org/3/reference/datamodel.html#creating-the-class-object
If you want to get clever you could return a namespace object from your
metaclass __prepare__ that triggers the descriptor protocol as you expected.
However, I would not recommend that. Getting clever with metaclasses is best
avoided. The default behavior is much simpler. That won't be changing.
> It looks to me like an undesirable asymmetry between the descriptors
> behaviour when in classes vs when in metaclasses. Is that intended? If it is,
> I think it should be highlighted in the descriptors documentation.
Regardless, metaclasses are used infrequently and combining them with
descriptors (especially relative to class definitions) is even less common. So
pointing out the caveats of this case may not be worth the time of all future
readers of those docs.
That said, clearly it would have helped you in this case. :) So here are some
*possible* places to clarify (very briefly):
* descriptors howto
+ about mixing descriptors with metaclasses
+ a list enumerating places where descriptors are *not* invoked
* language reference (metaclasses section)
+ a warning saying something like "Avoid metaclasses if you can help it and
only use them if you have a clear understanding of Python's object model (and
dotted access)"
* language reference (descriptors/dotted access section)
+ a list enumerating places where descriptors are *not* invoked
Which of those do you think would have helped you the most?
----------
nosy: +eric.snow
_______________________________________
Python tracker <[email protected]>
<https://bugs.python.org/issue39443>
_______________________________________
_______________________________________________
Python-bugs-list mailing list
Unsubscribe:
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com