如何覆盖模块名称(__name__)?

How to override module name (__name__)?

我在不同的文件中有两个 class,a.pyb.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.

我无法以任何方式修改a.py

# b.py
import a

class B(A):
    pass

日志输出为:

DEBUG:<TIME> called in a, Did something

我想更改 __name__ 以便 child class 的呼叫应该记录 called in b.

我的项目将其模块名称记录为输出的一部分。但是,当我继承 class 并调用 parent class 方法时,我不知道它是从 AB 调用的,因为它只显示 parent的模块名称。我该如何改变它?或者,还有其他方法可以避免这种情况?

有一种方法可以做到,但它比您想象的要复杂得多。

__name__ 这个名字从何而来?答案是它是一个全局变量。这是意料之中的,因为全局命名空间是模块命名空间。例如:

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

如果您在 Standard Type Hierarchy of python's Data Model 文档中查找可调用项,您会发现函数的 __globals__ 属性是只读的。这意味着函数 A.name 将始终在模块 a 的命名空间中查找其全局属性(并不是说模块命名空间本身是只读的)。

改变函数全局变量的唯一方法是复制函数对象。这已在 Stack Overflow 上至少讨论过几次:

  • How to create a copy of a python function
  • How can I make a deepcopy of a function in Python?

复制 classes 也出现了:

解决方案 1

我已经在我制作的名为 haggis. Specifically, haggis.objects.copy_class 的实用程序库中实现了这两种技术,它将执行您想要的操作:

from haggis.objects import copy_class
import a

class B(copy_class(A, globals(), __name__)):
    pass

这将复制 A。所有函数代码和非函数属性都将被直接引用。但是,函数对象将应用更新后的 __globals____module__ 属性。

此方法的主要问题是它稍微破坏了您预期的继承层次结构。从功能上讲,您不会看到任何差异,但 issubclass(b.B, a.A) 将不再成立。

解决方案 2(可能是最好的)

我可以看到复制全部 A 的方法并通过这样做打破继承层次会有点麻烦。幸运的是,还有另一种方法,haggis.objects.copy_func,可以帮助您从 A:

中复制 name 方法
from haggis.objects import copy_func
import a

class B(A):
    name = copy_func(A.name, globals(), __name__)

这个解决方案更健壮一些,因为复制 classes 比复制 python 中的函数要复杂得多。它相当高效,因为 name 的两个版本实际上共享相同的代码对象:只有元数据(包括 __globals__)会有所不同。

解决方案 3

如果您可以稍微改变 A,您就有了一个更简单的解决方案。 python 中的每个(正常)class 都有一个 __module__ 属性,即定义它的模块的 __name__ 属性。

您可以将 A 重写为

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

使用 self.__module__ 而不是 __name__ 大致类似于使用 type(self) 而不是 __class__

解决方案 4

另一个简单的想法是正确覆盖 A.name:

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

当然,如果 # Do something 是一项没有在其他地方实施的复杂任务,我当然知道这将无法正常工作。直接从 A.

复制函数对象可能更有效

解决方案:

尝试使用 inspect.stack:

import inspect
class A:
    def __init__(self):
        pass
    def name(self):
        # Do something
        print("Did in %s" % inspect.stack()[1].filename.rpartition('/')[-1][:-3])

现在 运行 b.py 会输出:

Did in b

解释:

inspect 可以检查 python 脚本中的活动对象,如 docs:

中所述

Return a list of frame records for the caller’s stack. The first entry in the returned list represents the caller; the last entry represents the outermost call on the stack.

它还提到它输出一个 NamedTuple 像:

FrameInfo(frame, filename, lineno, function, code_context, index)

所以我们可以通过索引这个元组的第二个元素来获取实时文件名,这将给出完整路径,所以我们必须将它拆分为路径并使用[=从完整路径中提取文件名19=].

编辑:

不编辑a.py,只能试试:

b.py:

import inspect
exec(inspect.getsource(__import__('a')))
class B(A):
    pass

它会给出:

Did in b