Programming
python python-3.x attributes python-2.x
Updated Sun, 29 May 2022 14:53:14 GMT

Get complete list of all possible Class Attributes


Is there a way, given a simple Class, to output all the possible attributes for it? Standard attributes like __class__ and __doc__ and special read only attributes like __mro__, __bases__ et al. Generally, all present attributes?

Considering the most simplistic case for a Class:

class myClass:
    pass

The dir(), vars() and inspect.getmembers() all exclude certain builtin attributes. The most complete list is offered by using myClass.__dir__(MyClass) which, while adding built in attributes, excludes user defined attributes of MyClass, for example:

In [3]: set(MyClass.__dir__(MyClass)) - set(dir(MyClass))
Out[3]: 
{'__abstractmethods__', '__base__', '__bases__',
 '__basicsize__', '__call__', '__dictoffset__',
 '__flags__', '__instancecheck__', '__itemsize__',
 '__mro__', '__name__', '__prepare__', '__qualname__',
 '__subclasscheck__', '__subclasses__', '__text_signature__',
 '__weakrefoffset__', 'mro'}

According to one of the added similar questions, this is not possible. If presently still not possible, what is the rationale behind "hiding" certain attributes like __bases__ (from standard calls to dir(), vars() & inspect and not ones like __name__?


Similar Questions:




Solution

dir() is basically a convenience method, it is not supposed to return everything, what it basically does is that it recursively returns everything found in the dictionary of a class and its bases.

PyPy's implementation of dir() is quite easy to understand:

def dir(*args):
    ...
    elif isinstance(obj, (types.TypeType, types.ClassType)):
        # Don't look at __class__, as metaclass methods would be confusing.
        return sorted(_classdir(obj))
    ...
def _classdir(klass):
    """Return a set of the accessible attributes of class/type klass.
    This includes all attributes of klass and all of the base classes
    recursively.
    """
    names = set()
    ns = getattr(klass, '__dict__', None)
    if ns is not None:
        names.update(ns)
    bases = getattr(klass, '__bases__', None)
    if bases is not None:
        # Note that since we are only interested in the keys, the order
        # we merge classes is unimportant
        for base in bases:
            names.update(_classdir(base))
    return names

As each class basically inherits from object you will see some dunder methods included because they are actually part of object's dictionary:

>>> class A(object):
    pass
...
>>> set(dir(A)) == set(list(object.__dict__) + list(A.__dict__))
True

Now what about __bases__ and other missing items?

First of all object itself is an instance of something, well it's bit of a mess actually:

>>> isinstance(type, object)
True
>>> isinstance(object, type)
True
>>> issubclass(type, object)
True
>>> issubclass(object, type)
False
>>> type.mro(object)
[<type 'object'>]
>>> type.mro(type)
[<type 'type'>, <type 'object'>]

So, all of the attributes like __bases__, __ge__ etc are actually part of type:

>>> list(type.__dict__)
['__module__', '__abstractmethods__', '__getattribute__', '__weakrefoffset__', '__dict__', '__lt__', '__init__', '__setattr__', '__subclasses__', '__new__', '__base__', '__mro__', 'mro', '__dictoffset__', '__call__', '__itemsize__', '__ne__', '__instancecheck__', '__subclasscheck__', '__gt__', '__name__', '__eq__', '__basicsize__', '__bases__', '__flags__', '__doc__', '__delattr__', '__le__', '__repr__', '__hash__', '__ge__']

Hence when we do A.__bases__ we are actually looking up a descriptor on type of A, i.e type:

>>> A.__bases__
(<type 'object'>,)
>>> type(A).__dict__['__bases__'].__get__(A, type)
(<type 'object'>,)

So, as A is an instance of type these methods are not part of its own dictionary but its type's dictionary.

>> class A(object):
...     spam = 'eggs'
...
>>> a = A()
>>> a.foo = 100
>>> a.bar = 200
>>> a.__dict__
{'foo': 100, 'bar': 200}
>>> A.__dict__
dict_proxy({'__dict__': <attribute '__dict__' of 'A' objects>, '__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None, 'spam': 'eggs'})

As, type is a subclass of object, the dir() call on type will contain some items from object:

>>> set(dir(type)) - set(type.__dict__)
set(['__reduce_ex__', '__str__', '__format__', '__reduce__', '__class__', '__subclasshook__', '__sizeof__'])




Comments (5)

  • +0 – I see, so my initial assumption that they are hidden is pretty wrong, attributes like __bases__ are just inherited from type and so calling a function like dir() on a subclass of type just won't return the inherited attributes. Also, regarding the existence of a function that fetches everything, I'm guessing the short answer is that if I want a function like that, better implement it myself because it doesn't exist, right? — Oct 12, 2015 at 21:02  
  • +0 – @DimitrisJim Almost correct, but "dir() on a subclass of type" should be "dir() on an instance of type", for example if you create a metaclass that has some attributes then its instances(aka classes) won't have those attributes visible to us but the attribute access will still work. No, there's no such function available, at least not in standard library. — Oct 13, 2015 at 05:42  
  • +0 – O.k, gotcha with that. The only thing I still don't get (which maybe I should ask as another question) is, given again an empty class definition, why certain inherited attributes from type like __ ge__ are visible in __dict__ and or __dir__ and others like __bases__ are not. — Oct 13, 2015 at 07:22  
  • +1 – @DimitrisJim Because they are part of object's __dict__, and other properties like __bases__ are not: set(dir(A)) == set(list(object.__dict__) + list(A.__dict__)). Python 2: ideone.com/TR0YPh ; Python 3: ideone.com/tmlHfW — Oct 13, 2015 at 08:51  
  • +0 – Thanks, Ashwini. I think I've got it. I'll ask a related question if I get lost again. Good day! — Oct 13, 2015 at 10:19