[Python-checkins] r86566 - in python/branches/py3k: Doc/glossary.rst Doc/library/inspect.rst Lib/inspect.py Lib/test/test_inspect.py Misc/NEWS Misc/python-wing4.wpr

michael.foord python-checkins at python.org
Sat Nov 20 16:07:31 CET 2010


Author: michael.foord
Date: Sat Nov 20 16:07:30 2010
New Revision: 86566

Log:
Issue 9732: addition of getattr_static to the inspect module

Modified:
   python/branches/py3k/Doc/glossary.rst
   python/branches/py3k/Doc/library/inspect.rst
   python/branches/py3k/Lib/inspect.py
   python/branches/py3k/Lib/test/test_inspect.py
   python/branches/py3k/Misc/NEWS
   python/branches/py3k/Misc/python-wing4.wpr

Modified: python/branches/py3k/Doc/glossary.rst
==============================================================================
--- python/branches/py3k/Doc/glossary.rst	(original)
+++ python/branches/py3k/Doc/glossary.rst	Sat Nov 20 16:07:30 2010
@@ -435,6 +435,14 @@
       its first :term:`argument` (which is usually called ``self``).
       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 objects can change their value but keep their :func:`id`.  See
       also :term:`immutable`.

Modified: python/branches/py3k/Doc/library/inspect.rst
==============================================================================
--- python/branches/py3k/Doc/library/inspect.rst	(original)
+++ python/branches/py3k/Doc/library/inspect.rst	Sat Nov 20 16:07:30 2010
@@ -563,3 +563,70 @@
    entry in the list represents the caller; the last entry represents where the
    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

Modified: python/branches/py3k/Lib/inspect.py
==============================================================================
--- python/branches/py3k/Lib/inspect.py	(original)
+++ python/branches/py3k/Lib/inspect.py	Sat Nov 20 16:07:30 2010
@@ -1054,3 +1054,67 @@
 def trace(context=1):
     """Return a list of records for the stack below the current exception."""
     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)

Modified: python/branches/py3k/Lib/test/test_inspect.py
==============================================================================
--- python/branches/py3k/Lib/test/test_inspect.py	(original)
+++ python/branches/py3k/Lib/test/test_inspect.py	Sat Nov 20 16:07:30 2010
@@ -706,12 +706,162 @@
         locs = dict(locs or {}, inst=self.inst)
         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():
     run_unittest(
         TestDecorators, TestRetrievingSourceCode, TestOneliners, TestBuggyCases,
         TestInterpreterStack, TestClassesAndFunctions, TestPredicates,
         TestGetcallargsFunctions, TestGetcallargsMethods,
-        TestGetcallargsUnboundMethods)
+        TestGetcallargsUnboundMethods, TestGetattrStatic
+    )
 
 if __name__ == "__main__":
     test_main()

Modified: python/branches/py3k/Misc/NEWS
==============================================================================
--- python/branches/py3k/Misc/NEWS	(original)
+++ python/branches/py3k/Misc/NEWS	Sat Nov 20 16:07:30 2010
@@ -25,6 +25,8 @@
   complex zeros on systems where the log1p function fails to respect
   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
   version-specific online reference manual.
 

Modified: python/branches/py3k/Misc/python-wing4.wpr
==============================================================================
--- python/branches/py3k/Misc/python-wing4.wpr	(original)
+++ python/branches/py3k/Misc/python-wing4.wpr	Sat Nov 20 16:07:30 2010
@@ -5,7 +5,9 @@
 ##################################################################
 [project attributes]
 proj.directory-list = [{'dirloc': loc('..'),
-                        'excludes': [u'Lib/__pycache__'],
+                        'excludes': [u'Lib/__pycache__',
+                                     u'Doc/build',
+                                     u'build'],
                         'filter': '*',
                         'include_hidden': False,
                         'recursive': True,


More information about the Python-checkins mailing list