如何覆盖模块名称(__name__)?
How to override module name (__name__)?
我在不同的文件中有两个 class,a.py 和 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.
我无法以任何方式修改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 方法时,我不知道它是从 A
或 B
调用的,因为它只显示 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
我在不同的文件中有两个 class,a.py 和 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.
我无法以任何方式修改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 方法时,我不知道它是从 A
或 B
调用的,因为它只显示 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