summaryrefslogtreecommitdiffstats
path: root/Doc/library/inspect.rst
diff options
context:
space:
mode:
Diffstat (limited to 'Doc/library/inspect.rst')
-rw-r--r--Doc/library/inspect.rst67
1 files changed, 67 insertions, 0 deletions
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 @@ line.
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