Issue 9732: fetch the method resolution order from the type metaclass directly in getattr_static

This commit is contained in:
Michael Foord 2010-11-20 16:40:44 +00:00
parent 6bb9989ae3
commit e516265bbc
3 changed files with 28 additions and 21 deletions

View File

@ -587,26 +587,14 @@ but avoids executing code when it fetches attributes.
that raise AttributeError). It can also return descriptors objects
instead of instance members.
There are several cases that will break `getattr_static` or be handled
incorrectly. These are pathological enough not to worry about (i.e. if you do
any of these then you deserve to have everything break anyway):
The only known case that can cause `getattr_static` to trigger code execution,
and cause it to return incorrect results (or even break), is where a class uses
:data:`~object.__slots__` and provides a `__dict__` member using a property or
descriptor. If you find other cases please report them so they can be fixed
or documented.
* :data:`~object.__dict__` existing (e.g. as a property) but returning the
wrong dictionary or even returning something other than a
dictionary
* classes created with :data:`~object.__slots__` that have the `__slots__`
member deleted from the class, or a fake `__slots__` attribute
attached to the instance, or any other monkeying with
`__slots__`
* type objects that lie about their :term:`MRO`
.. note::
Classes that override :data:`~object.__mro__` as a property will have this
code executed by `getattr_static`.
Descriptors are not resolved (for example slot descriptors or
getset descriptors on objects implemented in C). The descriptor
`getattr_static` does not resolve descriptors, for example slot descriptors or
getset descriptors on objects implemented in C. The descriptor object
is returned instead of the underlying attribute.
You can handle these with code like the following. Note that

View File

@ -1060,6 +1060,9 @@ def trace(context=1):
_sentinel = object()
def _static_getmro(klass):
return type.__dict__['__mro__'].__get__(klass)
def _check_instance(obj, attr):
instance_dict = {}
try:
@ -1070,7 +1073,7 @@ def _check_instance(obj, attr):
def _check_class(klass, attr):
for entry in getmro(klass):
for entry in _static_getmro(klass):
try:
return entry.__dict__[attr]
except KeyError:
@ -1110,7 +1113,7 @@ def getattr_static(obj, attr, default=_sentinel):
if obj is klass:
# for types we check the metaclass too
for entry in getmro(type(klass)):
for entry in _static_getmro(type(klass)):
try:
return entry.__dict__[attr]
except KeyError:

View File

@ -867,6 +867,22 @@ class TestGetattrStatic(unittest.TestCase):
self.assertEqual(inspect.getattr_static(Something(), 'foo'), 3)
self.assertEqual(inspect.getattr_static(Something, 'foo'), 3)
def test_mro_as_property(self):
class Meta(type):
@property
def __mro__(self):
return (object,)
class Base(object):
foo = 3
class Something(Base, metaclass=Meta):
pass
self.assertEqual(inspect.getattr_static(Something(), 'foo'), 3)
self.assertEqual(inspect.getattr_static(Something, 'foo'), 3)
def test_main():
run_unittest(
TestDecorators, TestRetrievingSourceCode, TestOneliners, TestBuggyCases,