Python import 问题

##Python import 遇到的几个问题

python 作为一个脚本语言,具有很多优良的特性,笔者最喜欢的大概是他丰富的函数库,同时在工作中自己也会开发一些库来使用,这样就会经常用到 import 功能。譬如我们有如下两个文件:

1
2
#bar.py
bar_var=1

1
2
3
#foo.py
from bar import bar_var
foo_var=1

在 foo.py 中我们引入了 bar 中的 bar_var 的变量。下面我们介绍 python 引用的机制及常见的问题。

Python import 机制

python 在执行import 的时候实际上是将引入的文件执行了一遍,并将对应文件加入了 module 列表中,我们对 foo.py 文件做如下修改:

1
2
3
4
5
6
#foo.py
import sys
print "BEFORE",sys.modules.get('bar')
from bar import bar_var
print "AFTER",sys.modules.get('bar')
foo_var = 1

运行后出现如下结果:

1
2
3
➜  Python python foo.py
BEFORE None
AFTER <module 'bar' from '/Users/simon/Development/Python/bar.pyc'>

我们可以看到,在引入 bar模块之前在模块列表中是没有这个模块的,引用之后就在模块列表中增加了对应的模块名称。

Python 为了解决循环引用的问题,只要在程序中引用过,之后不会再次引用。我们对 foo.py 文件和 bar.py 文件修改如下:

1
2
3
#bar.py
print "impoted"
bar_var=1

1
2
3
4
#foo.py
from bar import bar_var
foo_var = 1
from bar import bar_var

执行结果如下:

1
2
➜  Python python foo.py
impoted

Python import 问题

如果我们将两个文件互相引用会出现什么问题呢?例如 bar.py 和 foo.py 文件如下:

1
2
3
#bar.py
from foo import foo_var
bar_var=1

1
2
3
#foo.py
from bar import bar_var
foo_var = 1

如果运行 foo.py 文件会报错如下:

1
2
3
4
5
6
7
8
9
➜  Python python foo.py
Traceback (most recent call last):
File "foo.py", line 2, in <module>
from bar import bar_var
File "/Users/simon/Development/Python/bar.py", line 2, in <module>
from foo import foo_var
File "/Users/simon/Development/Python/foo.py", line 2, in <module>
from bar import bar_var
ImportError: cannot import name bar_var

通过异常链我们可以看到在执行 python foo.py 文件的时候程序的运行过程,包括以下几步:

  1. foo.py 文件执行到第2行发现需要引用 bar 模块,
  2. 查找 sys.modules 字典发现没有引入对应的模块,则引入 bar.py 文件,并添加到 sys.modules
  3. bar.py 文件被引入后执行到第2行发现需要引用 foo 模块
  4. 查找 sys.modules 字典发现没有引入对应的模块,则引入 foo.py 文件
  5. foo.py 文件被引入后执行到第2行发现需要引用 bar 模块
  6. 查找 sys.modules 字典发现已经引入了 bar 模块,则直接将bar 模块中的 bar_var 赋值给当前正在引入的 foo.py 文件
  7. 已经引入的 bar 模块还没有执行到第三行,因此没有 bar_var 这个变量的值,赋值失败,报错

如果将文件内容修改为如下代码,则不会出错:

1
2
3
#bar.py
bar_var=1
from foo import foo_var

1
2
3
#foo.py
from bar import bar_var
foo_var = 1

运行结果如下:

1
2
➜  Python python foo.py
➜ Python

运行流程如下:

  1. foo.py 文件执行到第2行发现需要引用 bar 模块,
  2. 查找 sys.modules 字典发现没有引入对应的模块,则引入 bar.py 文件,并添加到 sys.modules
  3. bar.py 文件被引入后执行到第3行发现需要引用 foo 模块
  4. 查找 sys.modules 字典发现没有引入对应的模块,则引入 foo.py 文件
  5. foo.py 文件被引入后执行到第2行发现需要引用 bar 模块
  6. 查找 sys.modules 字典发现已经引入了 bar 模块,则直接将bar 模块中的 bar_var 赋值给当前正在引入的 foo.py 文件
  7. bar 模块中已经执行到了第3行,已经对 bar_var 变量进行了赋值,foo 模块成功引入了 bar_var
  8. foo模块执行到第3行对 foo_var 进行赋值,返回 bar模块
  9. 返回 foo.py 文件