Search code examples
pythoninternals

How to get actual list of names of object if custom __dir__ implemented?


Official docs says:

If the object has a method named __dir__(), this method will be called and must return the list of attributes. This allows objects that implement a custom __getattr__() or __getattribute__() function to customize the way dir() reports their attributes.

If custom __dir__ implemented, results, returning by another function, inspect.getmembers(), also affected.

For example:

class С(object):
    __slots__ = ['atr']
    def __dir__(self):
        return ['nothing']
    def method(self):
        pass
    def __init__(self):
        self.atr = 'string'

c = C() 
print dir(f) #If we try this - well get ['nothing'] returned by custom __dir__()
print inspect.getmembers(f) #Here we get []
print f.__dict__ #And here - exception will be raised because of __slots__

How in this case list of names of object might be getted?


Solution

  • Answer to original question- does inspect.getmembers() use __dir__() like dir() does?

    Here's the source code for inspect.getmembers() so we can see what it's really doing:

    def getmembers(object, predicate=None):
        """Return all members of an object as (name, value) pairs sorted by name.                                                                                                                                     
        Optionally, only return members that satisfy a given predicate."""
        results = []
        for key in dir(object):
            try:
                value = getattr(object, key)
            except AttributeError:
                continue
            if not predicate or predicate(value):
                results.append((key, value))
        results.sort()
        return results
    

    From this we see that it is using dir() and just filtering the results a bit.

    How to get attributes with an overridden __dir__()?

    According to this answer, it isn't possible to always get a complete list of attributes, but we can still definitely get them in some cases/get enough to be useful.

    From the docs:

    If the object does not provide __dir__(), the function tries its best to gather information from the object’s __dict__ attribute, if defined, and from its type object. The resulting list is not necessarily complete, and may be inaccurate when the object has a custom __getattr__().

    So if you are not using __slots__, you could look at your object's __dict__ (and it's type object's) to get basically the same info that dir() would normally give you. So, just like with dir(), you would have to use a more rigorous method to get metaclass methods.

    If you are using __slots__, then getting class attributes is, in a way, a bit more simple. Yes, there's no dict, but there is __slots__ itself, which contains the names of all of the attributes. For example, adding print c.__slots__ to your example code yields ['atr']. (Again, a more rigorous approach is needed to get the attributes of superclasses as well.)

    How to get methods

    You might need a different solution depending on the use case, but if you just want to find out the methods easily, you can simply use the builtin help().

    Modified PyPy dir()

    Here's an alternative to some of the above: To get a version of dir() that ignores user-defined __dir__ methods, you could just take PyPy's implementation of dir() and delete the parts that reference __dir__ methods.