mirror of
https://github.com/python/cpython.git
synced 2025-10-09 00:22:17 +00:00
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.
This commit is contained in:
parent
c93dbe2f9b
commit
b0c84cdaac
4 changed files with 226 additions and 31 deletions
|
@ -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:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue