Search code examples
pythonpython-3.xclassinspectptpython

How to correctly "stub" __objclass__ in a Python class?


I have Python class looking somewhat like this:

class some_class:
    def __getattr__(self, name):
        # Do something with "name" (by passing it to a server)

Sometimes, I am working with ptpython (an interactive Python shell) for debugging. ptpython inspects instances of the class and tries to access the __objclass__ attribute, which does not exist. In __getattr__, I could simply check if name != "__objclass__" before working with name, but I'd like to know whether there is a better way by either correctly implementing or somehow stubbing __objclass__.

The Python documentation does not say very much about it, or at least I do not understand what I have to do:

The attribute __objclass__ is interpreted by the inspect module as specifying the class where this object was defined (setting this appropriately can assist in runtime introspection of dynamic class attributes). For callables, it may indicate that an instance of the given type (or a subclass) is expected or required as the first positional argument (for example, CPython sets this attribute for unbound methods that are implemented in C).


Solution

  • You want to avoid interfering with this attribute. There is no reason to do any kind of stubbing manually - you want to get out of the way and let it do what it usually does. If it behaves like attributes usually do, everything will work correctly.

    The correct implementation is therefore to special-case the __objclass__ attribute in your __getattr__ function and throw an AttributeError.

    class some_class:
        def __getattr__(self, name):
            if name == "__objclass__":
                raise AttributeError
    
            # Do something with "name" (by passing it to a server)
    

    This way it will behave the same way as it would in a class that has no __getattr__: The attribute is considered non-existant by default, until it's assigned to. The __getattr__ method won't be called if the attribute already exists, so it can be used without any issues:

    >>> obj = some_class()
    >>> hasattr(obj, '__objclass__')
    False
    >>> obj.__objclass__ = some_class
    >>> obj.__objclass__
    <class '__main__.some_class'>