Python的引用机制
Python作为解释型语言和Java等编译型语言的引用机制有很大区别,本文将浅析Python的引用机制,方便我们避免引用异常。想写这个是因为我在开发过程中遇到的一个问题,下面的文章也已解决问题为线索展开。
错误
在工作中写了一段与下面示例类似的代码并出现了引用异常。
错误信息可以通过执行下面示例代码中的t2.py复现,报错信息为ImportError: cannot import name 'create_b'。
t1.py
from t2 import create_b
class A:
def __init__(self, v1):
self.v1 = v1
self.v2 = create_b(v1)
class B:
def __init__(self, v1):
self.v1 = v1
if __name__ == '__main__':
a = A('test')
t2.py
from t1 import B
def create_b(v1):
v_temp = v1+'modified'
return B(v_temp)
错误分析
通过阅读代码和搜索错误信息(主要看了这篇文章), 我发现引发问题的原因是两个模块相互引用。 当运行t1.py时,他会去引入t2.py; 在t2.py运行时,会去引入t1.py中的B(); 但是此时t1.py中的class B还没有载入到内存中,无法找到,导致了引用异常。
修复错误的方法有两种:
- 使用
import xxx代替from xxx import xxx - 将引用放到模块末尾
下面是使用第二种方法的示例代码。
t1.py
class A:
def __init__(self, v1):
self.v1 = v1
self.v2 = create_b(v1)
class B:
def __init__(self, v1):
self.v1 = v1
from t2 import create_b
if __name__ == '__main__':
a = A('test')
t2.py
def create_b(v1):
v_temp = v1 + 'modified'
return B(v_temp)
from t1 import B
Python 引用机制
Python作为一个动态语言会在执行代码时处理引用,引用主要分为下面两种形式:
import x, 这里称为第1类引用from x import xxx, 这里成为第二类引用
这两种形式的引用略有不同,但总体引用流程相同,主要处理流程分为以下几步:
- 当引用一个模块时,python会先从
sys.modules中查找当前上下文是否已经存在。 - 对于第1类引用,如果目标模块已经存在(不关模块是否已经完全载入),则只需在当前上下文建立一个对该模块
x的索引即可;对于第2类引用则需要建立一个对x.xxx的引用。 - 当模块不存在时,会找到该模块并执行,将执行后获得的对象加入到
sys.modules中。当导入时遇到其他import子句的时候,则会递归的执行上述步骤。当模块导入成功后,会重新进行第二步。
另外,需要注意的是,python在引入modules的时候会执行模块级别的代码(模块方法,模块变量等),但是对于模块的Functions,在引入代码时并不会被真正执行,而只是记录方法的声明并建立连接,真正的方法代码是在方法被调用时才会执行。
希望读完上面的介绍,我们可以更好的组织代码间的引用关系。除了循环引用之外,Python关于引用的另外一个难点是绝对引用和相对引用,感兴趣的读者也可以自己去查询一下,或者读我后续发布的关于这两种引用的文章。