summaryrefslogtreecommitdiffstats
path: root/Lib/inspect.py
diff options
context:
space:
mode:
authorEthan Furman <ethan@stoneleaf.us>2013-10-21 05:37:39 (GMT)
committerEthan Furman <ethan@stoneleaf.us>2013-10-21 05:37:39 (GMT)
commitb0c84cdaac987e075099ac65a218505e9efbdda3 (patch)
treea960c83f11a9a52c4b9036d892c96ce3ed26e392 /Lib/inspect.py
parentc93dbe2f9b73a00b114e21564b32545898ef356c (diff)
downloadcpython-b0c84cdaac987e075099ac65a218505e9efbdda3.zip
cpython-b0c84cdaac987e075099ac65a218505e9efbdda3.tar.gz
cpython-b0c84cdaac987e075099ac65a218505e9efbdda3.tar.bz2
Issue #19030: final pieces for proper location of various class attributes located in the metaclass.
Okay, hopefully the very last patch for this issue. :/ I realized when playing with Enum that the metaclass attributes weren't always displayed properly. New patch properly locates DynamicClassAttributes, virtual class attributes (returned by __getattr__ and friends), and metaclass class attributes (if they are also in the metaclass __dir__ method). Also had to change one line in pydoc to get this to work. Added tests in test_inspect and test_pydoc to cover these situations.
Diffstat (limited to 'Lib/inspect.py')
-rw-r--r--Lib/inspect.py55
1 files changed, 32 insertions, 23 deletions
diff --git a/Lib/inspect.py b/Lib/inspect.py
index 2e3a670..edbf927 100644
--- a/Lib/inspect.py
+++ b/Lib/inspect.py
@@ -269,9 +269,9 @@ def getmembers(object, predicate=None):
results = []
processed = set()
names = dir(object)
- # add any virtual attributes to the list of names if object is a class
+ # :dd any DynamicClassAttributes to the list of names if object is a class;
# this may result in duplicate entries if, for example, a virtual
- # attribute with the same name as a member property exists
+ # attribute with the same name as a DynamicClassAttribute exists
try:
for base in object.__bases__:
for k, v in base.__dict__.items():
@@ -329,79 +329,88 @@ def classify_class_attrs(cls):
If one of the items in dir(cls) is stored in the metaclass it will now
be discovered and not have None be listed as the class in which it was
- defined.
+ defined. Any items whose home class cannot be discovered are skipped.
"""
mro = getmro(cls)
metamro = getmro(type(cls)) # for attributes stored in the metaclass
metamro = tuple([cls for cls in metamro if cls not in (type, object)])
- possible_bases = (cls,) + mro + metamro
+ class_bases = (cls,) + mro
+ all_bases = class_bases + metamro
names = dir(cls)
- # add any virtual attributes to the list of names
+ # :dd any DynamicClassAttributes to the list of names;
# this may result in duplicate entries if, for example, a virtual
- # attribute with the same name as a member property exists
+ # attribute with the same name as a DynamicClassAttribute exists.
for base in mro:
for k, v in base.__dict__.items():
if isinstance(v, types.DynamicClassAttribute):
names.append(k)
result = []
processed = set()
- sentinel = object()
+
for name in names:
# Get the object associated with the name, and where it was defined.
# Normal objects will be looked up with both getattr and directly in
# its class' dict (in case getattr fails [bug #1785], and also to look
# for a docstring).
- # For VirtualAttributes on the second pass we only look in the
+ # For DynamicClassAttributes on the second pass we only look in the
# class's dict.
#
# Getting an obj from the __dict__ sometimes reveals more than
# using getattr. Static and class methods are dramatic examples.
homecls = None
- get_obj = sentinel
- dict_obj = sentinel
+ get_obj = None
+ dict_obj = None
if name not in processed:
try:
if name == '__dict__':
- raise Exception("__dict__ is special, we don't want the proxy")
+ raise Exception("__dict__ is special, don't want the proxy")
get_obj = getattr(cls, name)
except Exception as exc:
pass
else:
homecls = getattr(get_obj, "__objclass__", homecls)
- if homecls not in possible_bases:
+ if homecls not in class_bases:
# if the resulting object does not live somewhere in the
# mro, drop it and search the mro manually
homecls = None
last_cls = None
- last_obj = None
- for srch_cls in ((cls,) + mro):
+ # first look in the classes
+ for srch_cls in class_bases:
srch_obj = getattr(srch_cls, name, None)
- if srch_obj is get_obj:
+ if srch_obj == get_obj:
+ last_cls = srch_cls
+ # then check the metaclasses
+ for srch_cls in metamro:
+ try:
+ srch_obj = srch_cls.__getattr__(cls, name)
+ except AttributeError:
+ continue
+ if srch_obj == get_obj:
last_cls = srch_cls
- last_obj = srch_obj
if last_cls is not None:
homecls = last_cls
- for base in possible_bases:
+ for base in all_bases:
if name in base.__dict__:
dict_obj = base.__dict__[name]
- homecls = homecls or base
+ if homecls not in metamro:
+ homecls = base
break
if homecls is None:
# unable to locate the attribute anywhere, most likely due to
# buggy custom __dir__; discard and move on
continue
+ obj = get_obj or dict_obj
# Classify the object or its descriptor.
- if get_obj is not sentinel:
- obj = get_obj
- else:
- obj = dict_obj
if isinstance(dict_obj, staticmethod):
kind = "static method"
+ obj = dict_obj
elif isinstance(dict_obj, classmethod):
kind = "class method"
- elif isinstance(obj, property):
+ obj = dict_obj
+ elif isinstance(dict_obj, property):
kind = "property"
+ obj = dict_obj
elif isfunction(obj) or ismethoddescriptor(obj):
kind = "method"
else: