最新消息:XAMPP默认安装之后是很不安全的,我们只需要点击左方菜单的 "安全"选项,按照向导操作即可完成安全设置。

Python中的import问题的本质理解

XAMPP案例 admin 377浏览 0评论

1. import的问题


在Python中,import是必不可少的,但是在自己写模块的时候,经常出现各种烦人import问题。

例如目录:

drg0000091

首先需要说明的是这个__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问题的本质理解

您必须 登录 才能发表评论!