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.
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 @@
     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 @@
 
     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
-                            last_obj = srch_obj
+                    # 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
                     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: