Furhter updates

This commit is contained in:
Ronald Oussoren 2015-07-25 13:17:34 +02:00
parent 2042d9e44a
commit 3a6f06fe6e
1 changed files with 81 additions and 20 deletions

View File

@ -56,7 +56,8 @@ Rationale
It is currently not possible to influence how the `super class`_ looks It is currently not possible to influence how the `super class`_ looks
up attributes (that is, ``super.__getattribute__`` unconditionally up attributes (that is, ``super.__getattribute__`` unconditionally
peeks in the class ``__dict__``), and that can be problematic for peeks in the class ``__dict__``), and that can be problematic for
dynamic classes that can grow new methods on demand. dynamic classes that can grow new methods on demand, for example dynamic
proxy classes.
The ``__getdescriptor__`` method makes it possible to dynamically add The ``__getdescriptor__`` method makes it possible to dynamically add
attributes even when looking them up using the `super class`_. attributes even when looking them up using the `super class`_.
@ -113,7 +114,8 @@ The attribute resolution proces as implemented by ``object.__getattribute__``
straightforward, but not entirely so without reading C code. straightforward, but not entirely so without reading C code.
The current CPython implementation of object.__getattribute__ is basicly The current CPython implementation of object.__getattribute__ is basicly
equivalent to the following (pseudo-) Python code (excluding some house keeping and speed tricks):: equivalent to the following (pseudo-) Python code (excluding some house
keeping and speed tricks)::
def _PyType_Lookup(tp, name): def _PyType_Lookup(tp, name):
@ -269,6 +271,9 @@ this.
In C code In C code
--------- ---------
A new type flag ``Py_TPFLAGS_GETDESCRIPTOR`` with value ``(1UL << 11)`` that
indicates that the new slot is present and to be used.
A new slot ``tp_getdescriptor`` is added to the ``PyTypeObject`` struct, this A new slot ``tp_getdescriptor`` is added to the ``PyTypeObject`` struct, this
slot corresponds to the ``__getdescriptor__`` method on `type`_. slot corresponds to the ``__getdescriptor__`` method on `type`_.
@ -281,6 +286,9 @@ superclasses, and should not invoke descriptors. The method returns ``NULL``
without setting an exception when the *name* cannot be found, and returns a without setting an exception when the *name* cannot be found, and returns a
new reference otherwise (not a borrowed reference). new reference otherwise (not a borrowed reference).
Classes with a ``tp_getdescriptor`` slot must add ``Py_TPFLAGS_GETDESCRIPTOR``
to ``tp_flags`` to indicate that new slot must be used.
Use of this hook by the interpreter Use of this hook by the interpreter
----------------------------------- -----------------------------------
@ -300,12 +308,16 @@ Because of this ``_PyType_Lookup`` will be renamed to ``_PyType_LookupName``,
this will cause compile-time errors for all out-of-tree users of this this will cause compile-time errors for all out-of-tree users of this
private API. private API.
For the same reason ``_PyType_LookupId`` is renamed to ``_PyType_LookupId2``.
A number of other functions in typeobject.c with the same issue do not get
an updated name because they are private to that file.
The attribute lookup cache in ``Objects/typeobject.c`` is disabled for classes The attribute lookup cache in ``Objects/typeobject.c`` is disabled for classes
that have a metaclass that overrides ``__getdescriptor__``, because using the that have a metaclass that overrides ``__getdescriptor__``, because using the
cache might not be valid for such classes. cache might not be valid for such classes.
Impact of this PEP on introspection Impact of this PEP on introspection
----------------------------------- ===================================
Use of the method introduced in this PEP can affect introspection of classes Use of the method introduced in this PEP can affect introspection of classes
with a metaclass that uses a custom ``__getdescriptor__`` method. This section with a metaclass that uses a custom ``__getdescriptor__`` method. This section
@ -334,33 +346,46 @@ changes to the visible behaviour of the ``object.__getattribute__``.
be ignored and is another way in which the result of ``inspect.getattr_static`` be ignored and is another way in which the result of ``inspect.getattr_static``
can be different from that of ``builtin.getattr``. can be different from that of ``builtin.getattr``.
* ``inspect.getmembers`` and ``inspect.get_class_attrs`` * ``inspect.getmembers`` and ``inspect.classify_class_attrs``
Both of these functions directly access the class __dict__ of classes along Both of these functions directly access the class __dict__ of classes along
the MRO, and hence can be affected by a custom ``__getdescriptor__`` method. the MRO, and hence can be affected by a custom ``__getdescriptor__`` method.
**TODO**: I haven't fully worked out what the impact of this is, and if there Code with a custom ``__getdescriptor__`` method that want to play nice with
are mitigations for those using either updates to these functions, or these methods also needs to ensure that the ``__dict__`` is set up correctly
additional methods that users should implement to be fully compatible with when that is accessed directly by Python code.
these functions.
One possible mitigation is to have a custom ``__getattribute__`` for these Note that ``inspect.getmembers`` is used by ``pydoc`` and hence this can
classes that fills ``__dict__`` before returning and and defers to the affect runtime documentation introspection.
default implementation for other attributes.
* Direct introspection of the class ``__dict__`` * Direct introspection of the class ``__dict__``
Any code that directly access the class ``__dict__`` for introspection Any code that directly access the class ``__dict__`` for introspection
can be affected by a custom ``__getdescriptor__`` method. can be affected by a custom ``__getdescriptor__`` method, see the previous
item.
Performance impact Performance impact
------------------ ==================
**WARNING**: The benchmark results in this section are old, and will be updated **WARNING**: The benchmark results in this section are old, and will be updated
when I've ported the patch to the current trunk. I don't expect significant when I've ported the patch to the current trunk. I don't expect significant
changes to the results in this section. changes to the results in this section.
Micro benchmarks
----------------
`Issue 18181`_ has a micro benchmark as one of its attachments
(`pep447-micro-bench.py`_) that specifically tests the speed of attribute
lookup, both directly and through super.
Note that attribute lookup with deep class hierarchies is significantly slower
when using a custom ``__getdescriptor__`` method. This is because the
attribute lookup cache for CPython cannot be used when having this method.
Pybench
-------
The pybench output below compares an implementation of this PEP with the The pybench output below compares an implementation of this PEP with the
regular source tree, both based on changeset a5681f50bae2, run on an idle regular source tree, both based on changeset a5681f50bae2, run on an idle
machine an Core i7 processor running Centos 6.4. machine an Core i7 processor running Centos 6.4.
@ -579,10 +604,10 @@ the performance impact is minimal::
Alternative proposals Alternative proposals
--------------------- =====================
``__getattribute_super__`` ``__getattribute_super__``
.......................... --------------------------
An earlier version of this PEP used the following static method on classes:: An earlier version of this PEP used the following static method on classes::
@ -593,7 +618,7 @@ necessarily limited to working only with ``super.__getattribute__``.
Reuse ``tp_getattro`` Reuse ``tp_getattro``
..................... ---------------------
It would be nice to avoid adding a new slot, thus keeping the API simpler and It would be nice to avoid adding a new slot, thus keeping the API simpler and
easier to understand. A comment on `Issue 18181`_ asked about reusing the easier to understand. A comment on `Issue 18181`_ asked about reusing the
@ -605,16 +630,44 @@ That won't work because ``tp_getattro`` will look in the instance
This would mean that using ``tp_getattro`` instead of peeking the class This would mean that using ``tp_getattro`` instead of peeking the class
dictionaries changes the semantics of the `super class`_. dictionaries changes the semantics of the `super class`_.
Alternate placement of the new method Alternative placement of the new method
..................................... ---------------------------------------
This PEP proposes to add ``__getdescriptor__`` as a method on the metaclass. This PEP proposes to add ``__getdescriptor__`` as a method on the metaclass.
An alternative would be to add it as a class method on the class itself An alternative would be to add it as a class method on the class itself
(simular to how ``__new__`` is a `staticmethod`_ of the class and not a method (simular to how ``__new__`` is a `staticmethod`_ of the class and not a method
of the metaclass). of the metaclass).
The two are functionally equivalent, and there's something to be said about The advantage of using a method on the metaclass is that will give an error
not requiring the use of a meta class. when two classes on the MRO have different metaclasses that may have different
behaviors for ``__getdescriptor__``. With a normal classmethod that problem
would pass undetected while it might cause subtle errors when running the code.
History
=======
* 23-Jul-2015: Added type flag ``Py_TPFLAGS_GETDESCRIPTOR`` after talking
with Guido.
The new flag is primarily useful to avoid crashing when loading an extension
for an older version of CPython and could have positive speed implications
as well.
* Jul-2014: renamed slot to ``__getdescriptor__``, the old name didn't
match the naming style of other slots and was less descriptive.
Discussion threads
==================
* The initial version of the PEP was send with
Message-ID `<75030FAC-6918-4E94-95DA-67A88D53E6F5@mac.com>`_
* Further discusion starting at a message with
Message-ID `<5BB87CC4-F31B-4213-AAAC-0C0CE738460C@mac.com>`_
* And more discussion starting at message with
Message-ID `<00AA7433-C853-4101-9718-060468EBAC54@mac.com>`_
References References
@ -627,6 +680,12 @@ Copyright
This document has been placed in the public domain. This document has been placed in the public domain.
.. _`<75030FAC-6918-4E94-95DA-67A88D53E6F5@mac.com>`: http://marc.info/?l=python-dev&m=137510220928964&w=2
.. _`<5BB87CC4-F31B-4213-AAAC-0C0CE738460C@mac.com>`: https://mail.python.org/pipermail/python-ideas/2014-July/028420.html
.. _`<00AA7433-C853-4101-9718-060468EBAC54@mac.com>`: https://mail.python.org/pipermail/python-dev/2013-July/127321.html
.. _`Issue 18181`: http://bugs.python.org/issue18181 .. _`Issue 18181`: http://bugs.python.org/issue18181
.. _`super class`: http://docs.python.org/3/library/functions.html#super .. _`super class`: http://docs.python.org/3/library/functions.html#super
@ -652,3 +711,5 @@ This document has been placed in the public domain.
.. _`PyObjC`: http://pyobjc.sourceforge.net/ .. _`PyObjC`: http://pyobjc.sourceforge.net/
.. _`classmethod`: http://docs.python.org/3/library/functions.html#classmethod .. _`classmethod`: http://docs.python.org/3/library/functions.html#classmethod
.. _`pep447-micro-bench.py`: http://bugs.python.org/file40013/pep447-micro-bench.py