Search code examples
pythonclassoopglobal-variablespython-module

How to override module name (__name__)?


I have two classes in separate files, a.py and b.py.

# a.py
import logging

LOG = logging.getLogger(__name__)

class A:
    def name(self):
        # Do something
        LOG.debug("Did something") # LOG reads __name__ and print it.

I can not modify a.py in any way.

# b.py
import a

class B(A):
    pass

The log output is:

DEBUG:<TIME> called in a, Did something

I want to change __name__ so that a child class's call should log called in b.

My project logs its module name as part of the output. However, when I inherit a class and call the parent class method, I cannot know it is called from A or B because it only shows the parent's module name. How can I change it? Or, some other way to avoid this?


Solution

  • There is a way to do it, but it's more involved than you might think.

    Where does the name __name__ come from? The answer is that it's a global variable. This is to be expected, since the global namespace is the module namespace. For example:

    >>> globals()['__name__']
    '__main__'
    

    If you look up callables in the Standard Type Hierarchy of python's Data Model documentation, you will find that a function's __globals__ attribute is read-only. That means that the function A.name will always look up its global attributes in the namespace of module a (not that the module namespace itself is read-only).

    The only way to alter globals for a function is to copy the function object. This has been hashed out on Stack Overflow at least a couple of times:

    Copying classes has come up as well:

    Solution 1

    I've gone ahead and implemented both techniques in a utility library I made called haggis. Specifically, haggis.objects.copy_class will do what you want:

    from haggis.objects import copy_class
    import a
    
    class B(copy_class(A, globals(), __name__)):
        pass
    

    This will copy A. All function code and non-function attributes will be referenced directly. However, function objects will have the updated __globals__ and __module__ attributes applied.

    The major problem with this method is that it breaks your intended inheritance hierarchy slightly. Functionally, you won't see a difference, but issubclass(b.B, a.A) will no longer be true.

    Solution 2 (probably the best one)

    I can see how it would be a bit cumbersome to copy all the methods of A and break the inheritance hierarchy by doing this. Luckily, there is another method, haggis.objects.copy_func, which can help you copy just the name method out of A:

    from haggis.objects import copy_func
    import a
    
    class B(A):
        name = copy_func(A.name, globals(), __name__)
    

    This solution is a bit more robust, because copying classes is much more finicky than copying functions in python. It's fairly efficient because both versions of name will actually share the same code object: only the metadata, including __globals__, will be different.

    Solution 3

    You have a much simpler solution available if you can alter A ever so slightly. Every (normal) class in python has a __module__ attribute that is the __name__ attribute of the module where it was defined.

    You can just rewrite A as

    class A:
        def name(self):
            # Do something
            print("Did in %s" % self.__module__)
    

    Using self.__module__ instead of __name__ is roughly analogous to using type(self) instead of __class__.

    Solution 4

    Another simple idea is to properly override A.name:

    class B(A):
        def name(self):
            # Do something
            LOG.debug("Did something")
    

    Of course I can see how this would not work well if # Do something is a complex task that is not implemented elsewhere. It's probably much more efficient to just copy the function object directly out of A.