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:
Ronald Oussoren 2013-08-22 12:49:43 +02:00
parent fe9c18f3ef
commit c3e6947b82
1 changed files with 242 additions and 210 deletions

View File

@ -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