pythonpython-3.xoop

How can I reliably get the module and class of the current class method in Python, even with inheritance and dynamic class creation?


I've encountered situations where standard methods like __module__ and __class__ become unreliable due to inheritance hierarchies or metaclass-based class creation. I need a robust approach that can accurately identify the module and class, regardless of the complexity of the class structure. Here's an example of a potential issue:

class BaseClass:
    @classmethod
    def mymethod(cls):
        print(cls.__module__, cls.__name__)

class DerivedClass(BaseClass):
    pass

DerivedClass.mymethod()

This will output __main__ DerivedClass, which might not be the desired result if you need to differentiate between the base class and derived class methods.

I'm looking for a solution that can handle scenarios like these and provide accurate module and class information, even in the presence of inheritance, metaclasses, and other advanced Python constructs.


Solution

  • To get the module and class of the defining type, you can look at the qualified name. With this approach, the derived class could be defined in a separate module entirely.

    import logging
    
    class BaseClass:
        @classmethod
        def mymethod(cls):
            _, _, methodname, _ = logging.getLogger().findCaller()
            func = getattr(cls, methodname)
            print("module:", func.__module__)
            print("method name:", methodname)
            print("qualname:", func.__qualname__)
            print("defining class:", func.__qualname__.split(".")[0])
    
    class DerivedClass(BaseClass):
        pass
    
    DerivedClass.mymethod()
    

    Note that direct invocations of findCaller() were bugged until Python 3.11.

    A somewhat deviant approach which still works in older Python versions:

    class BaseClass:
        @classmethod
        def mymethod(cls):
            methodname = (lambda:0).__qualname__.split(".")[1]
            func = getattr(cls, methodname)
            print("module:", func.__module__)
            print("method name:", methodname)
            print("qualname:", func.__qualname__)
            print("defining class:", func.__qualname__.split(".")[0])
    
    class DerivedClass(BaseClass):
        pass
    
    DerivedClass.mymethod()