Issue 9732: addition of getattr_static to the inspect module
This commit is contained in:
parent
89197fe93c
commit
95fc51dfda
|
@ -435,6 +435,14 @@ Glossary
|
||||||
its first :term:`argument` (which is usually called ``self``).
|
its first :term:`argument` (which is usually called ``self``).
|
||||||
See :term:`function` and :term:`nested scope`.
|
See :term:`function` and :term:`nested scope`.
|
||||||
|
|
||||||
|
method resolution order
|
||||||
|
Method Resolution Order is the order in which base classes are searched
|
||||||
|
for a member during lookup. See `The Python 2.3 Method Resolution Order
|
||||||
|
<http://www.python.org/download/releases/2.3/mro/>`_.
|
||||||
|
|
||||||
|
MRO
|
||||||
|
See :term:`method resolution order`.
|
||||||
|
|
||||||
mutable
|
mutable
|
||||||
Mutable objects can change their value but keep their :func:`id`. See
|
Mutable objects can change their value but keep their :func:`id`. See
|
||||||
also :term:`immutable`.
|
also :term:`immutable`.
|
||||||
|
|
|
@ -563,3 +563,70 @@ line.
|
||||||
entry in the list represents the caller; the last entry represents where the
|
entry in the list represents the caller; the last entry represents where the
|
||||||
exception was raised.
|
exception was raised.
|
||||||
|
|
||||||
|
|
||||||
|
Fetching attributes statically
|
||||||
|
------------------------------
|
||||||
|
|
||||||
|
Both :func:`getattr` and :func:`hasattr` can trigger code execution when
|
||||||
|
fetching or checking for the existence of attributes. Descriptors, like
|
||||||
|
properties, will be invoked and :meth:`__getattr__` and :meth:`__getattribute__`
|
||||||
|
may be called.
|
||||||
|
|
||||||
|
For cases where you want passive introspection, like documentation tools, this
|
||||||
|
can be inconvenient. `getattr_static` has the same signature as :func:`getattr`
|
||||||
|
but avoids executing code when it fetches attributes.
|
||||||
|
|
||||||
|
.. function:: getattr_static(obj, attr, default=None)
|
||||||
|
|
||||||
|
Retrieve attributes without triggering dynamic lookup via the
|
||||||
|
descriptor protocol, `__getattr__` or `__getattribute__`.
|
||||||
|
|
||||||
|
Note: this function may not be able to retrieve all attributes
|
||||||
|
that getattr can fetch (like dynamically created attributes)
|
||||||
|
and may find attributes that getattr can't (like descriptors
|
||||||
|
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):
|
||||||
|
|
||||||
|
* :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__`
|
||||||
|
* objects that lie about their type by having `__class__` as a
|
||||||
|
descriptor (`getattr_static` traverses the :term:`MRO` of whatever type
|
||||||
|
`obj.__class__` returns instead of the real type)
|
||||||
|
* type objects that lie about their :term:`MRO`
|
||||||
|
|
||||||
|
Descriptors are not resolved (for example slot descriptors or
|
||||||
|
getset descriptors on objects implemented in C). The descriptor
|
||||||
|
is returned instead of the underlying attribute.
|
||||||
|
|
||||||
|
You can handle these with code like the following. Note that
|
||||||
|
for arbitrary getset descriptors invoking these may trigger
|
||||||
|
code execution::
|
||||||
|
|
||||||
|
# example code for resolving the builtin descriptor types
|
||||||
|
class _foo(object):
|
||||||
|
__slots__ = ['foo']
|
||||||
|
|
||||||
|
slot_descriptor = type(_foo.foo)
|
||||||
|
getset_descriptor = type(type(open(__file__)).name)
|
||||||
|
wrapper_descriptor = type(str.__dict__['__add__'])
|
||||||
|
descriptor_types = (slot_descriptor, getset_descriptor, wrapper_descriptor)
|
||||||
|
|
||||||
|
result = getattr_static(some_object, 'foo')
|
||||||
|
if type(result) in descriptor_types:
|
||||||
|
try:
|
||||||
|
result = result.__get__()
|
||||||
|
except AttributeError:
|
||||||
|
# descriptors can raise AttributeError to
|
||||||
|
# indicate there is no underlying value
|
||||||
|
# in which case the descriptor itself will
|
||||||
|
# have to do
|
||||||
|
pass
|
||||||
|
|
|
@ -1054,3 +1054,67 @@ def stack(context=1):
|
||||||
def trace(context=1):
|
def trace(context=1):
|
||||||
"""Return a list of records for the stack below the current exception."""
|
"""Return a list of records for the stack below the current exception."""
|
||||||
return getinnerframes(sys.exc_info()[2], context)
|
return getinnerframes(sys.exc_info()[2], context)
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------ static version of getattr
|
||||||
|
|
||||||
|
_sentinel = object()
|
||||||
|
|
||||||
|
def _check_instance(obj, attr):
|
||||||
|
instance_dict = {}
|
||||||
|
try:
|
||||||
|
instance_dict = object.__getattribute__(obj, "__dict__")
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
return instance_dict.get(attr, _sentinel)
|
||||||
|
|
||||||
|
|
||||||
|
def _check_class(klass, attr):
|
||||||
|
for entry in getmro(klass):
|
||||||
|
try:
|
||||||
|
return entry.__dict__[attr]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
return _sentinel
|
||||||
|
|
||||||
|
|
||||||
|
def getattr_static(obj, attr, default=_sentinel):
|
||||||
|
"""Retrieve attributes without triggering dynamic lookup via the
|
||||||
|
descriptor protocol, __getattr__ or __getattribute__.
|
||||||
|
|
||||||
|
Note: this function may not be able to retrieve all attributes
|
||||||
|
that getattr can fetch (like dynamically created attributes)
|
||||||
|
and may find attributes that getattr can't (like descriptors
|
||||||
|
that raise AttributeError). It can also return descriptor objects
|
||||||
|
instead of instance members in some cases. See the
|
||||||
|
documentation for details.
|
||||||
|
"""
|
||||||
|
instance_result = _sentinel
|
||||||
|
if not isinstance(obj, type):
|
||||||
|
instance_result = _check_instance(obj, attr)
|
||||||
|
klass = obj.__class__
|
||||||
|
else:
|
||||||
|
klass = obj
|
||||||
|
|
||||||
|
klass_result = _check_class(klass, attr)
|
||||||
|
|
||||||
|
if instance_result is not _sentinel and klass_result is not _sentinel:
|
||||||
|
if (_check_class(type(klass_result), '__get__') is not _sentinel and
|
||||||
|
_check_class(type(klass_result), '__set__') is not _sentinel):
|
||||||
|
return klass_result
|
||||||
|
|
||||||
|
if instance_result is not _sentinel:
|
||||||
|
return instance_result
|
||||||
|
if klass_result is not _sentinel:
|
||||||
|
return klass_result
|
||||||
|
|
||||||
|
if obj is klass:
|
||||||
|
# for types we check the metaclass too
|
||||||
|
for entry in getmro(type(klass)):
|
||||||
|
try:
|
||||||
|
return entry.__dict__[attr]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
if default is not _sentinel:
|
||||||
|
return default
|
||||||
|
raise AttributeError(attr)
|
||||||
|
|
|
@ -706,12 +706,162 @@ class TestGetcallargsUnboundMethods(TestGetcallargsMethods):
|
||||||
locs = dict(locs or {}, inst=self.inst)
|
locs = dict(locs or {}, inst=self.inst)
|
||||||
return (func, 'inst,' + call_params_string, locs)
|
return (func, 'inst,' + call_params_string, locs)
|
||||||
|
|
||||||
|
|
||||||
|
class TestGetattrStatic(unittest.TestCase):
|
||||||
|
|
||||||
|
def test_basic(self):
|
||||||
|
class Thing(object):
|
||||||
|
x = object()
|
||||||
|
|
||||||
|
thing = Thing()
|
||||||
|
self.assertEqual(inspect.getattr_static(thing, 'x'), Thing.x)
|
||||||
|
self.assertEqual(inspect.getattr_static(thing, 'x', None), Thing.x)
|
||||||
|
with self.assertRaises(AttributeError):
|
||||||
|
inspect.getattr_static(thing, 'y')
|
||||||
|
|
||||||
|
self.assertEqual(inspect.getattr_static(thing, 'y', 3), 3)
|
||||||
|
|
||||||
|
def test_inherited(self):
|
||||||
|
class Thing(object):
|
||||||
|
x = object()
|
||||||
|
class OtherThing(Thing):
|
||||||
|
pass
|
||||||
|
|
||||||
|
something = OtherThing()
|
||||||
|
self.assertEqual(inspect.getattr_static(something, 'x'), Thing.x)
|
||||||
|
|
||||||
|
def test_instance_attr(self):
|
||||||
|
class Thing(object):
|
||||||
|
x = 2
|
||||||
|
def __init__(self, x):
|
||||||
|
self.x = x
|
||||||
|
thing = Thing(3)
|
||||||
|
self.assertEqual(inspect.getattr_static(thing, 'x'), 3)
|
||||||
|
del thing.x
|
||||||
|
self.assertEqual(inspect.getattr_static(thing, 'x'), 2)
|
||||||
|
|
||||||
|
def test_property(self):
|
||||||
|
class Thing(object):
|
||||||
|
@property
|
||||||
|
def x(self):
|
||||||
|
raise AttributeError("I'm pretending not to exist")
|
||||||
|
thing = Thing()
|
||||||
|
self.assertEqual(inspect.getattr_static(thing, 'x'), Thing.x)
|
||||||
|
|
||||||
|
def test_descriptor(self):
|
||||||
|
class descriptor(object):
|
||||||
|
def __get__(*_):
|
||||||
|
raise AttributeError("I'm pretending not to exist")
|
||||||
|
desc = descriptor()
|
||||||
|
class Thing(object):
|
||||||
|
x = desc
|
||||||
|
thing = Thing()
|
||||||
|
self.assertEqual(inspect.getattr_static(thing, 'x'), desc)
|
||||||
|
|
||||||
|
def test_classAttribute(self):
|
||||||
|
class Thing(object):
|
||||||
|
x = object()
|
||||||
|
|
||||||
|
self.assertEqual(inspect.getattr_static(Thing, 'x'), Thing.x)
|
||||||
|
|
||||||
|
def test_inherited_classattribute(self):
|
||||||
|
class Thing(object):
|
||||||
|
x = object()
|
||||||
|
class OtherThing(Thing):
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.assertEqual(inspect.getattr_static(OtherThing, 'x'), Thing.x)
|
||||||
|
|
||||||
|
def test_slots(self):
|
||||||
|
class Thing(object):
|
||||||
|
y = 'bar'
|
||||||
|
__slots__ = ['x']
|
||||||
|
def __init__(self):
|
||||||
|
self.x = 'foo'
|
||||||
|
thing = Thing()
|
||||||
|
self.assertEqual(inspect.getattr_static(thing, 'x'), Thing.x)
|
||||||
|
self.assertEqual(inspect.getattr_static(thing, 'y'), 'bar')
|
||||||
|
|
||||||
|
del thing.x
|
||||||
|
self.assertEqual(inspect.getattr_static(thing, 'x'), Thing.x)
|
||||||
|
|
||||||
|
def test_metaclass(self):
|
||||||
|
class meta(type):
|
||||||
|
attr = 'foo'
|
||||||
|
class Thing(object, metaclass=meta):
|
||||||
|
pass
|
||||||
|
self.assertEqual(inspect.getattr_static(Thing, 'attr'), 'foo')
|
||||||
|
|
||||||
|
class sub(meta):
|
||||||
|
pass
|
||||||
|
class OtherThing(object, metaclass=sub):
|
||||||
|
x = 3
|
||||||
|
self.assertEqual(inspect.getattr_static(OtherThing, 'attr'), 'foo')
|
||||||
|
|
||||||
|
class OtherOtherThing(OtherThing):
|
||||||
|
pass
|
||||||
|
# this test is odd, but it was added as it exposed a bug
|
||||||
|
self.assertEqual(inspect.getattr_static(OtherOtherThing, 'x'), 3)
|
||||||
|
|
||||||
|
def test_no_dict_no_slots(self):
|
||||||
|
self.assertEqual(inspect.getattr_static(1, 'foo', None), None)
|
||||||
|
self.assertNotEqual(inspect.getattr_static('foo', 'lower'), None)
|
||||||
|
|
||||||
|
def test_no_dict_no_slots_instance_member(self):
|
||||||
|
# returns descriptor
|
||||||
|
with open(__file__) as handle:
|
||||||
|
self.assertEqual(inspect.getattr_static(handle, 'name'), type(handle).name)
|
||||||
|
|
||||||
|
def test_inherited_slots(self):
|
||||||
|
# returns descriptor
|
||||||
|
class Thing(object):
|
||||||
|
__slots__ = ['x']
|
||||||
|
def __init__(self):
|
||||||
|
self.x = 'foo'
|
||||||
|
|
||||||
|
class OtherThing(Thing):
|
||||||
|
pass
|
||||||
|
# it would be nice if this worked...
|
||||||
|
# we get the descriptor instead of the instance attribute
|
||||||
|
self.assertEqual(inspect.getattr_static(OtherThing(), 'x'), Thing.x)
|
||||||
|
|
||||||
|
def test_descriptor(self):
|
||||||
|
class descriptor(object):
|
||||||
|
def __get__(self, instance, owner):
|
||||||
|
return 3
|
||||||
|
class Foo(object):
|
||||||
|
d = descriptor()
|
||||||
|
|
||||||
|
foo = Foo()
|
||||||
|
|
||||||
|
# for a non data descriptor we return the instance attribute
|
||||||
|
foo.__dict__['d'] = 1
|
||||||
|
self.assertEqual(inspect.getattr_static(foo, 'd'), 1)
|
||||||
|
|
||||||
|
# if the descriptor is a data-desciptor we should return the
|
||||||
|
# descriptor
|
||||||
|
descriptor.__set__ = lambda s, i, v: None
|
||||||
|
self.assertEqual(inspect.getattr_static(foo, 'd'), Foo.__dict__['d'])
|
||||||
|
|
||||||
|
|
||||||
|
def test_metaclass_with_descriptor(self):
|
||||||
|
class descriptor(object):
|
||||||
|
def __get__(self, instance, owner):
|
||||||
|
return 3
|
||||||
|
class meta(type):
|
||||||
|
d = descriptor()
|
||||||
|
class Thing(object, metaclass=meta):
|
||||||
|
pass
|
||||||
|
self.assertEqual(inspect.getattr_static(Thing, 'd'), meta.__dict__['d'])
|
||||||
|
|
||||||
|
|
||||||
def test_main():
|
def test_main():
|
||||||
run_unittest(
|
run_unittest(
|
||||||
TestDecorators, TestRetrievingSourceCode, TestOneliners, TestBuggyCases,
|
TestDecorators, TestRetrievingSourceCode, TestOneliners, TestBuggyCases,
|
||||||
TestInterpreterStack, TestClassesAndFunctions, TestPredicates,
|
TestInterpreterStack, TestClassesAndFunctions, TestPredicates,
|
||||||
TestGetcallargsFunctions, TestGetcallargsMethods,
|
TestGetcallargsFunctions, TestGetcallargsMethods,
|
||||||
TestGetcallargsUnboundMethods)
|
TestGetcallargsUnboundMethods, TestGetattrStatic
|
||||||
|
)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
test_main()
|
test_main()
|
||||||
|
|
|
@ -25,6 +25,8 @@ Library
|
||||||
complex zeros on systems where the log1p function fails to respect
|
complex zeros on systems where the log1p function fails to respect
|
||||||
the sign of zero. This fixes a test failure on AIX.
|
the sign of zero. This fixes a test failure on AIX.
|
||||||
|
|
||||||
|
- Issue #9732: Addition of getattr_static to the inspect module.
|
||||||
|
|
||||||
- Issue #10446: Module documentation generated by pydoc now links to a
|
- Issue #10446: Module documentation generated by pydoc now links to a
|
||||||
version-specific online reference manual.
|
version-specific online reference manual.
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,9 @@
|
||||||
##################################################################
|
##################################################################
|
||||||
[project attributes]
|
[project attributes]
|
||||||
proj.directory-list = [{'dirloc': loc('..'),
|
proj.directory-list = [{'dirloc': loc('..'),
|
||||||
'excludes': [u'Lib/__pycache__'],
|
'excludes': [u'Lib/__pycache__',
|
||||||
|
u'Doc/build',
|
||||||
|
u'build'],
|
||||||
'filter': '*',
|
'filter': '*',
|
||||||
'include_hidden': False,
|
'include_hidden': False,
|
||||||
'recursive': True,
|
'recursive': True,
|
||||||
|
|
Loading…
Reference in New Issue