如果我们在模块 mod1 中有递归函数 func1 并且我们使用这两个语句导入它:
import mod1
和
from mod1 import func1
基本上我们将有两个指向单个对象的链接:
id(func1) == id(mod1.func1)
然后,如果我们更新 mod1.py 文件中的 func1 代码,然后重新加载 mod1,而不是函数 (func1) 本身:
imp.reload(mod1)
我们将有两个不同的对象:
id(func1) != id(mod1.func1)
但是,如果我们调用 func1(*args) - 首先调用它调用 func1,但随后的所有递归调用它调用 mod1.func1
这是 python 中的理想行为吗?
代码示例如下: mod1.py:
def func1(n):
print("n is: {}".format(n))
if n==0: return 1
return n*func1(n-1)
然后修改:
def func1(n):
# print("n is: {}".format(n))
if n==0: return 1
return n*func1(n-1)
这里是 REPL 输出:
>>> from mod1 import func1
>>> func1(2)
n is: 2
n is: 1
n is: 0
2
>>> import mod1
>>> mod1.func1(2)
n is: 2
n is: 1
n is: 0
2
>>> id(func1)
4304551720
>>> id(mod1.func1)
4304551720
>>> ## ** Here mod1 code is updated: ** ##
>>> import imp
__main__:1: DeprecationWarning: the imp module is deprecated in favour of importlib; see the module's documentation for alternative uses
>>> imp.reload(mod1)
<module 'mod1' from '/home/user/workspace/python/tests/mod1.py'>
>>> id(mod1.func1)
4305274128
>>> id(func1)
4304551720
>>> mod1.func1(2)
2
>>> func1(2)
n is: 2
2
>>>
好像有点乱。 这是 python 中的理想行为吗?
最佳答案
很难说这种行为是否(对谁)是可取的,但这是意料之中的。
您的代码示例与以下代码示例没有太大区别:
In [3]: class Foo:
...: def qwe(self, once_again=True):
...: print('original qwe')
...: if once_again:
...: self.qwe(once_again=False)
...: qwe1 = qwe
...:
...: def qwe(self, once_again=True):
...: print('new qwe')
...: if once_again:
...: self.qwe(once_again=False)
...: a = Foo()
...: a.qwe1()
...:
...:
original qwe
new qwe
import 语句执行后,mod1
和func1
只是保持对相应对象的引用的变量。重新加载模块时,只需将另一个值分配给名称为 mod1
的变量。
顺便说一句,当您在变量中存储对模块级别或类级别记录器的引用时,您可能会观察到类似的效果:LOG = logging.getLogger(__name__)
,因为没有什么能阻止用户您的代码在应用程序生命周期的中间某处调用 logging.config.dictConfig
。如果较新的配置是为了抑制模块的输出,您的代码将不知道关于模块的新记录器的任何信息,您将继续写入日志。
我会说,这种行为至少是有意为之的。
UPD:问题变得有点复杂。
如果变量在函数的当前词法范围内丢失,则从模块范围中获取它(如果它不是闭包,在这种情况下使用 __closure__
字段)。为了获得模块的范围,该函数通过 sys.modules[func1.__module__]
访问模块级变量,该变量已被 reload
更新。
关于从模块重新加载的Python递归函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53485604/