PEP 447: Slight rewording of the text
* Adds a clearer description of what would change to the abstract * Small wording tweaks in other parts * Reflow the document, lines with normal text are now shorter than 80 characters (the output from pybench and the links section still contain longer lines) * Don't use TAB characters for indentation.
This commit is contained in:
parent
fe9c18f3ef
commit
c3e6947b82
98
pep-0447.txt
98
pep-0447.txt
|
@ -18,6 +18,28 @@ in the ``__dict__`` of classes on the MRO for a class when looking for
|
||||||
an attribute. This PEP adds an optional ``__locallookup__`` method to
|
an attribute. This PEP adds an optional ``__locallookup__`` method to
|
||||||
a metaclass that can be used to override this behavior.
|
a metaclass that can be used to override this behavior.
|
||||||
|
|
||||||
|
That is, the MRO walking loop in ``_PyType_Lookup`` and
|
||||||
|
``super.__getattribute__`` gets changed from::
|
||||||
|
|
||||||
|
def lookup(mro_list, name):
|
||||||
|
for cls in mro_list:
|
||||||
|
if name in cls.__dict__:
|
||||||
|
return cls.__dict__
|
||||||
|
|
||||||
|
return NotFound
|
||||||
|
|
||||||
|
to::
|
||||||
|
|
||||||
|
def lookup(mro_list, name):
|
||||||
|
for cls in mro_list:
|
||||||
|
try:
|
||||||
|
return cls.__locallookup__(name)
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return NotFound
|
||||||
|
|
||||||
|
|
||||||
Rationale
|
Rationale
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
@ -30,7 +52,8 @@ The ``__locallookup__`` method makes it possible to dynamicly add
|
||||||
attributes even when looking them up using the `super class`_.
|
attributes even when looking them up using the `super class`_.
|
||||||
|
|
||||||
The new method affects ``object.__getattribute__`` (and
|
The new method affects ``object.__getattribute__`` (and
|
||||||
`PyObject_GenericGetAttr`_) as well for consistency.
|
`PyObject_GenericGetAttr`_) as well for consistency and to have a single
|
||||||
|
place to implement dynamic attribute resolution for classes.
|
||||||
|
|
||||||
Background
|
Background
|
||||||
----------
|
----------
|
||||||
|
@ -52,16 +75,22 @@ The superclass attribute lookup hook
|
||||||
====================================
|
====================================
|
||||||
|
|
||||||
Both ``super.__getattribute__`` and ``object.__getattribute__`` (or
|
Both ``super.__getattribute__`` and ``object.__getattribute__`` (or
|
||||||
`PyObject_GenericGetAttr`_ in C code) walk an object's MRO and peek in the
|
`PyObject_GenericGetAttr`_ and in particular ``_PyType_Lookup`` in C code)
|
||||||
class' ``__dict__`` to look up attributes. A way to affect this lookup is
|
walk an object's MRO and currently peek in the class' ``__dict__`` to look up
|
||||||
using a method on the meta class for the type, that by default looks up
|
attributes.
|
||||||
the name in the class ``__dict__``.
|
|
||||||
|
With this proposal both lookup methods no longer peek in the class ``__dict__``
|
||||||
|
but call the special method ``__locallookup__``, which is a slot defined
|
||||||
|
on the metaclass. The default implementation of that method looks
|
||||||
|
up the name the class ``__dict__``, which means that attribute lookup is
|
||||||
|
unchanged unless a metatype actually defines the new special method.
|
||||||
|
|
||||||
In Python code
|
In Python code
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
A meta type can define a method ``__locallookup__`` that is called during
|
A meta type can define a method ``__locallookup__`` that is called during
|
||||||
attribute resolution by both ``super.__getattribute__`` and ``object.__getattribute``::
|
attribute resolution by both ``super.__getattribute__``
|
||||||
|
and ``object.__getattribute``::
|
||||||
|
|
||||||
class MetaType(type):
|
class MetaType(type):
|
||||||
def __locallookup__(cls, name):
|
def __locallookup__(cls, name):
|
||||||
|
@ -70,18 +99,19 @@ attribute resolution by both ``super.__getattribute__`` and ``object.__getattrib
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise AttributeError(name) from None
|
raise AttributeError(name) from None
|
||||||
|
|
||||||
The ``__locallookup__`` method has as its arguments a class and the name of the attribute
|
The ``__locallookup__`` method has as its arguments a class (which is an
|
||||||
that is looked up. It should return the value of the attribute without invoking descriptors,
|
instance of the meta type) and the name of the attribute that is looked up.
|
||||||
or raise `AttributeError`_ when the name cannot be found.
|
It should return the value of the attribute without invoking descriptors,
|
||||||
|
and should raise `AttributeError`_ when the name cannot be found.
|
||||||
|
|
||||||
The `type`_ class provides a default implementation for ``__locallookup__``, that
|
The `type`_ class provides a default implementation for ``__locallookup__``,
|
||||||
looks up the name in the class dictionary.
|
that looks up the name in the class dictionary.
|
||||||
|
|
||||||
Example usage
|
Example usage
|
||||||
.............
|
.............
|
||||||
|
|
||||||
The code below implements a silly metaclass that redirects attribute lookup to uppercase
|
The code below implements a silly metaclass that redirects attribute lookup to
|
||||||
versions of names::
|
uppercase versions of names::
|
||||||
|
|
||||||
class UpperCaseAccess (type):
|
class UpperCaseAccess (type):
|
||||||
def __locallookup__(cls, name):
|
def __locallookup__(cls, name):
|
||||||
|
@ -101,44 +131,46 @@ versions of names::
|
||||||
In C code
|
In C code
|
||||||
---------
|
---------
|
||||||
|
|
||||||
A new slot ``tp_locallookup`` is added to the ``PyTypeObject`` struct, this slot
|
A new slot ``tp_locallookup`` is added to the ``PyTypeObject`` struct, this
|
||||||
corresponds to the ``__locallookup__`` method on `type`_.
|
slot corresponds to the ``__locallookup__`` method on `type`_.
|
||||||
|
|
||||||
The slot has the following prototype::
|
The slot has the following prototype::
|
||||||
|
|
||||||
PyObject* (*locallookupfunc)(PyTypeObject* cls, PyObject* name);
|
PyObject* (*locallookupfunc)(PyTypeObject* cls, PyObject* name);
|
||||||
|
|
||||||
This method should lookup *name* in the namespace of *cls*, without looking at superclasses,
|
This method should lookup *name* in the namespace of *cls*, without looking
|
||||||
and should not invoke descriptors. The method returns ``NULL`` without setting an exception
|
at superclasses, and should not invoke descriptors. The method returns ``NULL`` without setting an exception when the *name* cannot be found, and returns a
|
||||||
when the *name* cannot be found, and returns a new reference otherwise (not a borrowed reference).
|
new reference otherwise (not a borrowed reference).
|
||||||
|
|
||||||
Use of this hook by the interpreter
|
Use of this hook by the interpreter
|
||||||
-----------------------------------
|
-----------------------------------
|
||||||
|
|
||||||
The new method is required for metatypes and as such is defined on `type_`. Both
|
The new method is required for metatypes and as such is defined on `type_`.
|
||||||
``super.__getattribute__`` and ``object.__getattribute__``/`PyObject_GenericGetAttr`_
|
Both ``super.__getattribute__`` and
|
||||||
(through ``_PyType_Lookup``) use the this ``__locallookup__`` method when walking
|
``object.__getattribute__``/`PyObject_GenericGetAttr`_
|
||||||
the MRO.
|
(through ``_PyType_Lookup``) use the this ``__locallookup__`` method when
|
||||||
|
walking the MRO.
|
||||||
|
|
||||||
Other changes to the implementation
|
Other changes to the implementation
|
||||||
-----------------------------------
|
-----------------------------------
|
||||||
|
|
||||||
The change for `PyObject_GenericGetAttr`_ will be done by changing the private function
|
The change for `PyObject_GenericGetAttr`_ will be done by changing the private
|
||||||
``_PyType_Lookup``. This currently returns a borrowed reference, but must return a new
|
function ``_PyType_Lookup``. This currently returns a borrowed reference, but
|
||||||
reference when the ``__locallookup__`` method is present. Because of this ``_PyType_Lookup``
|
must return a new reference when the ``__locallookup__`` method is present.
|
||||||
will be renamed to ``_PyType_LookupName``, this will cause compile-time errors for all out-of-tree
|
Because of this ``_PyType_Lookup`` will be renamed to ``_PyType_LookupName``,
|
||||||
users of this private API.
|
this will cause compile-time errors for all out-of-tree users of this
|
||||||
|
private API.
|
||||||
|
|
||||||
The attribute lookup cache in ``Objects/typeobject.c`` is disabled for classes that have a
|
The attribute lookup cache in ``Objects/typeobject.c`` is disabled for classes
|
||||||
metaclass that overrides ``__locallookup__``, because using the cache might not be valid
|
that have a metaclass that overrides ``__locallookup__``, because using the
|
||||||
for such classes.
|
cache might not be valid for such classes.
|
||||||
|
|
||||||
Performance impact
|
Performance impact
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
The pybench output below compares an implementation of this PEP with the regular
|
The pybench output below compares an implementation of this PEP with the
|
||||||
source tree, both based on changeset a5681f50bae2, run on an idle machine an
|
regular source tree, both based on changeset a5681f50bae2, run on an idle
|
||||||
Core i7 processor running Centos 6.4.
|
machine an Core i7 processor running Centos 6.4.
|
||||||
|
|
||||||
Even though the machine was idle there were clear differences between runs,
|
Even though the machine was idle there were clear differences between runs,
|
||||||
I've seen difference in "minimum time" vary from -0.1% to +1.5%, with simular
|
I've seen difference in "minimum time" vary from -0.1% to +1.5%, with simular
|
||||||
|
|
Loading…
Reference in New Issue