覆盖后续导入导入的函数?

Overriding functions imported by a subsequent import?

对于一个项目,我需要从一个模块中替换一个函数。我想通过以下最小示例来实现这一点

mod.py中定义了原函数,

def f():
    print("mod.py")

应该被 mod2.py 中的 f2 覆盖,

import sys

def f2():
    print("mod2.py")

sys.modules["mod"].f = f2

main.py中,我首先加载了原始函数,我立即对其进行了修补

from mod import f
import mod2

f()  # I'm expecting "mod2.py", but get "mod.py" instead

__main__ 的 globals() 中 f 的函数定义似乎不是对 sys.modules["mod"].f 的引用,这是我希望的。

如果在 main.py 中导入定义了 f 的模块,而不是 f 本身,我可以修补它

import mod
import mod2

print(mod.f())  # patched from mod2

但我想找到一个解决方案,它既可以在 main.py 中导入,也可以导入整个模块 (import mod) 和函数本身 (from mod import f)。

我还尝试通过 inspect.currentframe().f_back.f_locals 以某种方式修改父框架中的 locals() 但这似乎效果不佳,如果我找到这样的解决方案,我会觉得很麻烦,这会做我想要的。

原则上,Python 处理 import 的方式可行吗?

from mod import f 获取 mod.f 引用的对象,并添加绑定到当前 module 中名为 f 的新变量。该对象现在至少有两个引用,mod.ff。如果 mod.f 被重新分配,原始函数对象引用计数减 1,但它仍然被本地 f 引用,因此仍然是任何人取消引用时使用的对象 f.

你可以有一个 module 知道你想做什么猴子补丁并让它导入 mod 和 mod2。然后先导入那个 module。事实上,mod2.py 可以 import mod。这一切都相当脆弱,因为您的代码的用户需要知道要注意导入内容的顺序。

最好不要 from mod import f。只需 import mod 并在需要时执行 mod.f。在那种情况下,您没有对函数的不同引用,并且修补工作正常。

mod.py

def f():
    print("original")

def other():
    print("other in mod")

mod2.py

from mod import *
def f():
    print("override")

main.py

#all of mod is in mod2, no need to import mod unless you need the original f()
import mod2
#if you do need the original f()
from mod import f

f() #original
mod2.f() #override
mod2.other() #other in mod

以程序方式覆盖所有这些东西很好,但使用 class 继承可能更有帮助。

class Mod:
    def __init__(self):
        pass
        
    def f(self, data):
        #do something with data
        data.reverse()
        
        print(f'in Mod {data}')
        

class Mod2(Mod):
    def __init__(self):
        Mod.__init__(self)
        
    def f(self, data, skip=False):
        if not skip:
            #example of doing something that is not included in original f()
            data.pop(0)
            
            print(f'in Mod2 {data}')
            
        super().f(data) #call the original f() that this f() overrides


m2 = Mod2()

m2.f([11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1])
#in Mod2 [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
#in Mod [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

m2.f([11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1], True)
#in Mod [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]

这是对 mod.py 中的函数进行猴子修补的好方法,这样它将影响 import mod2 之后 mod 的任何进一步导入。这是可行的,因为模块缓存在 sys.modules 中 — 但如果您按照所示进行操作,则不需要显式引用它。

mod.py:

def f():
    print("mod.py")

mod2.py:

import mod

def f2():
    print("mod2.py")

# Monkey-patch `mod` module.
mod.f = f2

main.py:

import mod
import mod2  # Importing this applies the patch to the `mod` module.

mod.f()  # Prints -> mod2.py