Issue 9732: addition of getattr_static to the inspect module
diff --git a/Doc/glossary.rst b/Doc/glossary.rst
index ec75e03..4b0ab70 100644
--- a/Doc/glossary.rst
+++ b/Doc/glossary.rst
@@ -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`.
diff --git a/Doc/library/inspect.rst b/Doc/library/inspect.rst
index 1f5e72e..2f09348 100644
--- a/Doc/library/inspect.rst
+++ b/Doc/library/inspect.rst
@@ -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