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?

Answer

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.



Source: stackoverflow