174 lines
5.6 KiB
Plaintext
174 lines
5.6 KiB
Plaintext
PEP: 447
|
|
Title: Hooking into super attribute resolution
|
|
Version: $Revision$
|
|
Last-Modified: $Date$
|
|
Author: Ronald Oussoren <ronaldoussoren@mac.com>
|
|
Status: Draft
|
|
Type: Standards Track
|
|
Content-Type: text/x-rst
|
|
Created: 12-Jun-2013
|
|
Post-History: 2-Jul-2013, 15-Jul-2013
|
|
|
|
|
|
Abstract
|
|
========
|
|
|
|
In current python releases the attribute resolution of the `super class`_
|
|
peeks in the ``__dict__`` attribute of classes on the MRO to look
|
|
for attributes. This PEP introduces a hook that classes can use
|
|
to override that behavior for specific classes.
|
|
|
|
|
|
Rationale
|
|
=========
|
|
|
|
Peeking in the class ``__dict__`` works for regular classes, but can
|
|
cause problems when a class dynamically looks up attributes in a
|
|
``__getattribute__`` method.
|
|
|
|
This new hook makes it possible to affect attribute lookup for both normal
|
|
attribute lookup and lookup through the `super class`_.
|
|
|
|
|
|
The superclass attribute lookup hook
|
|
====================================
|
|
|
|
Both ``super.__getattribute__`` and ``object.__getattribute__`` (or
|
|
`PyObject_GenericGetAttr`_ in C code) walk an object's MRO and peek in the
|
|
class' ``__dict__`` to look up attributes. A way to affect this lookup is
|
|
using a method on the meta class for the type, that by default looks up
|
|
the name in the class ``__dict__``.
|
|
|
|
In Python code
|
|
--------------
|
|
|
|
A meta type can define a method ``__locallookup__`` that is called during
|
|
attribute resolution by both ``super.__getattribute__`` and ``object.__getattribute``::
|
|
|
|
class MetaType(type):
|
|
|
|
def __locallookup__(cls, name):
|
|
try:
|
|
return cls.__dict__[name]
|
|
except KeyError:
|
|
raise AttributeError(name) from None
|
|
|
|
The example method above is pseudocode for the implementation of this method on
|
|
`type`_ (the actual implementation is in C, and doesn't suffer from the recursion
|
|
problem in this example).
|
|
|
|
The ``__locallookup__`` method has as its arguments a class and the name of the attribute
|
|
that is looked up. It should return the value of the attribute without invoking descriptors,
|
|
or raise `AttributeError`_ when the name cannot be found.
|
|
|
|
|
|
In C code
|
|
---------
|
|
|
|
A new slot ``tp_locallookup`` is added to the ``PyTypeObject`` struct, this slot
|
|
corresponds to the ``__locallookup__`` method on `type`_.
|
|
|
|
The slot has the following prototype::
|
|
|
|
PyObject* (*locallookupfunc)(PyTypeObject* cls, PyObject* name);
|
|
|
|
This method should lookup *name* in the namespace of *cls*, without looking at superclasses,
|
|
and should not invoke descriptors. The method returns ``NULL`` without setting an exception
|
|
when the *name* cannot be found, and returns a new reference otherwise (not a borrowed reference).
|
|
|
|
|
|
Usage of this hook
|
|
------------------
|
|
|
|
The new method will be defined for `type`_, and will peek in the class dictionary::
|
|
|
|
static PyObject*
|
|
type_locallookup(PyTypeObject* cls, PyObject* name)
|
|
{
|
|
PyObject* res;
|
|
if (!cls->tp_dict) {
|
|
return NULL;
|
|
}
|
|
|
|
res = PyDict_GetItem(cls, name);
|
|
Py_XINCREF(res);
|
|
return res;
|
|
}
|
|
|
|
The new method will be used by both `PyObject_GenericGetAttr`_ and
|
|
``super.__getattribute__`` instead of peeking in a type's ``tp_dict``.
|
|
|
|
|
|
Alternative proposals
|
|
---------------------
|
|
|
|
``__getattribute_super__``
|
|
..........................
|
|
|
|
An earlier version of this PEP used the following static method on classes::
|
|
|
|
def __getattribute_super__(cls, name, object, owner): pass
|
|
|
|
This method performed name lookup as well as invoking descriptors and was necessarily
|
|
limited to working only with ``super.__getattribute__``.
|
|
|
|
|
|
Reuse ``tp_getattro``
|
|
.....................
|
|
|
|
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
|
|
``tp_getattro`` slot, that is super could call the ``tp_getattro`` slot of all
|
|
methods along the MRO.
|
|
|
|
That won't work because ``tp_getattro`` will look in the instance
|
|
``__dict__`` before it tries to resolve attributes using classes in the MRO.
|
|
This would mean that using ``tp_getattro`` instead of peeking the class
|
|
dictionaries changes the semantics of the `super class`_.
|
|
|
|
|
|
Open Issues
|
|
===========
|
|
|
|
* The names of the new slot and magic method are far from settled.
|
|
|
|
* Should the python method raise an exception or return a magic value (such as the
|
|
`NotImplemented`_ return value used by comparison operators). The latter could be
|
|
slightly faster because it doesn't have to overhead of setting up exception state, but
|
|
makes it impossible to use that value as an attribute on a class.
|
|
|
|
* The proposed change to `PyObject_GenericGetAttr`_ will probably cause problems with the
|
|
attribute lookup cache (MCACHE):
|
|
|
|
1. That code stores borrowed references, which won't work when the hook is present. That
|
|
is mostly fixable, but at the cost of possibly keeping garbage alive.
|
|
|
|
2. Caching isn't an option when a hook might execute arbitrary code (and there hence is
|
|
no reason to assume that the hooks return value won't change later on).
|
|
|
|
The only workaround I could find for this is to make the hook a fallback (that is,
|
|
more like ``__getattr__`` than ``__getattribute__``).
|
|
|
|
References
|
|
==========
|
|
|
|
* `Issue 18181`_ contains a partial prototype implementation
|
|
|
|
|
|
Copyright
|
|
=========
|
|
|
|
This document has been placed in the public domain.
|
|
|
|
.. _`Issue 18181`: http://bugs.python.org/issue18181
|
|
|
|
.. _`super class`: http://docs.python.org/3/library/functions.html#super
|
|
|
|
.. _`NotImplemented`: http://docs.python.org/3/library/constants.html#NotImplemented
|
|
|
|
.. _`PyObject_GenericGetAttr`: http://docs.python.org/3/c-api/object.html#PyObject_GenericGetAttr
|
|
|
|
.. _`type`: http://docs.python.org/3/library/functions.html#type
|
|
|
|
.. _`AttributeError`: http://docs.python.org/3/library/exceptions.html#AttributeError
|