1. import的问题
在Python中,import是必不可少的,但是在自己写模块的时候,经常出现各种烦人import问题。
例如目录:
首先需要说明的是这个__init__.py文件,可能很多开发工程师都不太理解这个文件的作用,这个文件的存在表示该目录是一个包,这个很关键。
接着看样例代码,lib2.py:
def func2():
print('func2')
lib1.py:
from .lib2 import func2
def func1():
print('func1')
if __name__ == '__main__':
func1()
func2()
test.py:
from test_lib.lib1 import func1
func1()
这时,在项目目录运行:
python test.py
这样是没问题的,但是如果直接运行lib1.py,则会报错:
cd test_lib
python lib1.py
会得到报错信息:
Traceback (most recent call last):
File "lib1.py", line 3, in <module>
from .lib2 import func2
ImportError: attempted relative import with no known parent package
这句“from .lib2 import func2”是在当前目录中找lib2模块(相对导入),对应的文件明明就是存在,为什么会报错呢?
2. __name__变量的问题
解决上面的问题不难,在测试时直接运行lib1.py,将“from .lib2 import func2”改为“from lib2 import func2”就能正常运行了,测试完成在改回去。
这确实可以解决,但是很不优雅,特别是对我这种记性不好的,测试完可能就忘记了也可能。这重复劳动也不该是我们程序员该干的活(DRY)。
我们把lib2.py改成这样:
print(f'__name__: {__name__} in ./test_lib/lib2.py')
def func2():
print('func2')
直接运行该文件会得到:
__name__: __main__ in ./test_lib/lib2.py
这个__name__是模块名字(这是我的理解),显然如果直接运行,其模块名字就是__main__,而不是lib2。这也是我们为什么可以直接使用“if __name__ == ‘__main__’:”来测试的原因,其他非直接运行的模块名则是文件名。
下面我们来验证这个结果,先修改lib1.py:
from lib2 import func2
if __name__ == '__main__':
print(f'__name__: {__name__} in ./test_lib/lib1.py')
这时,直接运行python lib1.py,会得到输出:
__name__: lib2 in ./test_lib/lib2.py
__name__: __main__ in ./test_lib/lib1.py
显然,直接被运行的文件,其模块名为__main__,而不直接运行的,其模块名为对应的文件名。python是怎么根据lib2来找到对应的模块的呢?
其实对于每一个需要引用的包或者模块,python都会按一定的顺序去相应的目录去搜索,具体可以将sys.path的值打印出来看看。例如:
['/home/deeao/test_lib',
'/home/alex/.local/lib/python3.8/site-packages',
'/usr/local/lib/python3.8/dist-packages',
'/usr/lib/python3/dist-packages'
]
其中第一个目录就是当前文件所在目录,在该目录下就能找到lib2.py文件。
那么问题来了,
3. 为什么不能使用相对引用呢?
这时需要引用另一个很少用到的特殊变量“__package__”了,先修改lib2.py:
print(f'__name__: {__name__} in ./test_lib/lib2.py')
print(f'__package__: {__package__} in ./test_lib/lib2.py')
def func2():
print('func2')
直接运行该文件,得到:
__name__: __main__ in ./test_lib/lib2.py
__package__: None in ./test_lib/lib2.py
显然,直接运行时,该变量的值为None,该值表示为顶级包。
我们再修改lib1.py:
from lib2 import func2
def func1():
print('func1')
print(f'__name__: {__name__} in ./test_lib/lib1.py')
print(f'__package__: {__package__} in ./test_lib/lib1.py')
if __name__ == '__main__':
pass
这时直接运行lib1.py,会得到:
__name__: lib2 in ./test_lib/lib2.py
__package__: in ./test_lib/lib2.py
__name__: __main__ in ./test_lib/lib1.py
__package__: None in ./test_lib/lib1.py
这时lib2所在的package为空字符串,表示在当前目录下。
而我们回到上级目录直接运行test.py(需要先将修改lib1.py中的引用为:from .lib2 import func2),会得到:
__name__: test_lib.lib2 in ./test_lib/lib2.py
__package__: test_lib in ./test_lib/lib2.py
__name__: test_lib.lib1 in ./test_lib/lib1.py
__package__: test_lib in ./test_lib/lib1.py
__name__: __main__ in ./test.py
这时,lib1和lib2都在test_lib这个package(包)下了,这个时候在lib1中导入lib2中的对象时就可以使用相对导入了。
这就很清楚了,相对导入不是相对于目录,而是相对于package。
对此官方也有相应的解释:
Relative imports use a module’s __name__
attribute to determine that module’s position in the package hierarchy. If the module’s name does not contain any package information (e.g. it is set to'__main__'
) then relative imports are resolved as if the module were a top level module, regardless of where the module is actually located on the file system.
(对于英语不好的我,理解有难度)
4. 小结
首先,需要分清两个概念:
- 包(package):__init__.py所在的目录。
- 模块:对应python文件。
而特别需要注意的是,直接被运行的python文件,其包名是None(顶级包)。
import的规则:
- 按sys.path中的路径顺序进行搜索。
- 使用相对导入的时候,这个相对的意义是相对于package,而不是相对于当前路径的。
还有要理解两个变量:
- __name__
- __package__
看完这个,妈妈再也不用担心你掉进“相对导入”的坑里了。
转载请注明:XAMPP中文组官网 » Python中的import问题的本质理解