diff options
author | Ethan Furman <ethan@stoneleaf.us> | 2013-10-21 05:37:39 (GMT) |
---|---|---|
committer | Ethan Furman <ethan@stoneleaf.us> | 2013-10-21 05:37:39 (GMT) |
commit | b0c84cdaac987e075099ac65a218505e9efbdda3 (patch) | |
tree | a960c83f11a9a52c4b9036d892c96ce3ed26e392 /Lib/inspect.py | |
parent | c93dbe2f9b73a00b114e21564b32545898ef356c (diff) | |
download | cpython-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.py | 55 |
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: |