背景
本文意在将python基础基础进行简单的归纳和介绍,属于个人总结,并无技术含量,请老司机绕道而行。
内容
什么是python包
其实我们在将模块的时候已经解释过这个了,这里再次阐述一下:Python引入了按目录来组织模块的方法,称为包(Package),来避免模块名冲突,比如在项目合作中与其他人写的模块名冲突,这个可能性其实挺高的,比如我写了一个runsql.py,他也写了一个runsql.py。
引入了包以后,只要顶层的包名不与别人冲突,那所有模块都不会与别人冲突。每一个包目录下面都会有一个__init__.py的文件,这个文件是必须存在的,否则,Python就把这个目录当成普通目录,而不是一个python包。__init__.py 可以是空文件,也可以有Python代码,因为__init__.py 本身就是一个模块。
Python项目下的包管理的正确引用方式
假设我有如下Python项目:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
[root@happy_test_hostname demo-python-project]# tree -L 3 package/ package/ ├── app.py ├── __init__.py ├── __main__.py ├── subpackage1 │ ├── foo1.py │ ├── __init__.py │ └── subsubpackage11 │ ├── foo11.py │ └── __init__.py └── subpackage2 ├── foo2.py └── __init__.py 3 directories, 9 files |
1 2 3 4 5 6 7 8 9 10 11 12 |
# foo1 代码 [root@happy_test_hostname subpackage2]# cat ../subpackage1/foo1.py #!/usr/bin/python # -*- coding: UTF-8 -*- def speak(): print("This is foo1 speaking, I'm from pakcage.subpackage1") if __name__ == "__main__": speak() |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# foo2 代码 [root@happy_test_hostname subpackage2]# cat foo2.py #!/usr/bin/python # -*- coding: UTF-8 -*- from package.subpackage1 import foo1 def speak(): print("This is foo2 speaking, I'm from pakcage.subpackage2") print("I invoked foo1: ") foo1.speak() speak() |
如上,假设package是我们的一个python项目,我想让subpackage2中的foo2来import subpackage1中的foo1,上面的这种方式直接运行肯定是不行的:
1 2 3 4 5 |
[root@happy_test_hostname subpackage2]# python foo2.py Traceback (most recent call last): File "foo2.py", line 4, in <module> from package.subpackage1 import foo1 ImportError: No module named 'package' |
方式1:修改PYTHONPATH系统环境变量:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
[root@happy_test_hostname subpackage2]# pwd /root/demo-python-project/package/subpackage2 [root@happy_test_hostname subpackage2]# [root@happy_test_hostname subpackage2]# export PYTHONPATH="/root/demo-python-project/package" [root@happy_test_hostname subpackage2]# [root@happy_test_hostname subpackage2]# [root@happy_test_hostname subpackage2]# echo $PYTHONPATH /root/demo-python-project/package [root@happy_test_hostname subpackage2]# cat foo2.py #!/usr/bin/python # -*- coding: UTF-8 -*- from subpackage1 import foo1 def speak(): print("This is foo2 speaking, I'm from pakcage.subpackage2") print("I invoked foo1: ") foo1.speak() speak() [root@happy_test_hostname subpackage2]# python foo2.py This is foo2 speaking, I'm from pakcage.subpackage2 I invoked foo1: This is foo1 speaking, I'm from pakcage.subpackage1 [root@happy_test_hostname subpackage2]# |
方式2: 使用sys.path.append的方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
[root@happy_test_hostname subpackage2]# cat foo2.py #!/usr/bin/python # -*- coding: UTF-8 -*- import sys sys.path.append("../") from subpackage1 import foo1 def speak(): print("This is foo2 speaking, I'm from pakcage.subpackage2") print("I invoked foo1: ") foo1.speak() speak() [root@happy_test_hostname subpackage2]# python foo2.py This is foo2 speaking, I'm from pakcage.subpackage2 I invoked foo1: This is foo1 speaking, I'm from pakcage.subpackage1 |
方式3: 使用python -m的方式
首先,解释一下python -m 的作用, -m参数会把你输入命令的目录(也就是当前路径),放到sys.path。 我们来举个例子。
一般情况下,我不用-m参数,当我正常的指定脚本执行时,它默认会将目标脚本(foo2.py)所在的目录(subpackage2)的绝对路径加入 sys.path 中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
[root@happy_test_hostname package]# pwd /root/demo-python-project/package [root@happy_test_hostname package]# [root@happy_test_hostname package]# cat subpackage2/foo2.py #!/usr/bin/python # -*- coding: UTF-8 -*- import sys sys.path.append("../") from subpackage1 import foo1 print(sys.path) def speak(): print("This is foo2 speaking, I'm from pakcage.subpackage2") print("I invoked foo1: ") foo1.speak() speak() [root@happy_test_hostname package]# [root@happy_test_hostname package]# python subpackage2/foo2.py; # 正常指定脚本执行 ['/root/demo-python-project/package/subpackage2', '/root/demo-python-project/package', '/root/.pyenv/versions/3.4.1/lib/python34.zip', '/root/.pyenv/versions/3.4.1/lib/python3.4', '/root/.pyenv/versions/3.4.1/lib/python3.4/plat-linux', '/root/.pyenv/versions/3.4.1/lib/python3.4/lib-dynload', '/root/.pyenv/versions/3.4.1/lib/python3.4/site-packages', '../'] This is foo2 speaking, I'm from pakcage.subpackage2 I invoked foo1: This is foo1 speaking, I'm from pakcage.subpackage1 [root@happy_test_hostname package]# |
如果我加上-m以后,则会将我指定的路径目录package作为一个模块导入sys.path中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
[root@happy_test_hostname demo-python-project]# pwd /root/demo-python-project [root@happy_test_hostname demo-python-project]# [root@happy_test_hostname demo-python-project]# cat package/subpackage2/foo2.py #!/usr/bin/python # -*- coding: UTF-8 -*- from package.subpackage1 import foo1 # 直接按照包路径导入哦 import sys print(sys.path) def speak(): print("This is foo2 speaking, I'm from pakcage.subpackage2") print("I invoked foo1: ") foo1.speak() speak() [root@happy_test_hostname demo-python-project]# python -m package.subpackage2.foo2 ['', '/root/demo-python-project/package', '/root/.pyenv/versions/3.4.1/lib/python34.zip', '/root/.pyenv/versions/3.4.1/lib/python3.4', '/root/.pyenv/versions/3.4.1/lib/python3.4/plat-linux', '/root/.pyenv/versions/3.4.1/lib/python3.4/lib-dynload', '/root/.pyenv/versions/3.4.1/lib/python3.4/site-packages'] This is foo2 speaking, I'm from pakcage.subpackage2 I invoked foo1: This is foo1 speaking, I'm from pakcage.subpackage1 [root@happy_test_hostname demo-python-project]# |
也就是说,如果用-m参数的形式的话,我可以在整个项目的应用中直接按包路径来import,也就是说,即使你想在app.py中导入subsubpackage11下的foo11,我也可以直接按照包路径导入:
1 |
from package.subpackage1.subsubpackage11 import foo11 |
哇,是不是感觉好清晰啊。
但是,凡事有利有弊吧,如果你想用 -m 的这种方式,那么你只能在package所在的目录下运行 python -m 命令。
1 2 3 4 5 6 7 8 9 10 11 |
[root@happy_test_hostname demo-python-project]# python -m package.subpackage2.foo2 ['', '/root/demo-python-project/package', '/root/.pyenv/versions/3.4.1/lib/python34.zip', '/root/.pyenv/versions/3.4.1/lib/python3.4', '/root/.pyenv/versions/3.4.1/lib/python3.4/plat-linux', '/root/.pyenv/versions/3.4.1/lib/python3.4/lib-dynload', '/root/.pyenv/versions/3.4.1/lib/python3.4/site-packages'] This is foo2 speaking, I'm from pakcage.subpackage2 I invoked foo1: This is foo1 speaking, I'm from pakcage.subpackage1 [root@happy_test_hostname demo-python-project]# [root@happy_test_hostname demo-python-project]# cd package/ [root@happy_test_hostname package]# [root@happy_test_hostname package]# python -m package.subpackage2.foo2 /root/.pyenv/versions/3.4.1/bin/python: Error while finding spec for 'package.subpackage2.foo2' (<class 'ImportError'>: No module named 'package') [root@happy_test_hostname package]# |
上面的三个方法都可以实现python项目中的包模块导入,各有利弊,但是个人比较偏向于第3种吧,因为在代码中看起来更清晰一些。
- 如果你用PYTHONPATH,那么当有多个项目时,你需要把每个项目的根目录都加入到PYTHONPATH中,会使得PYTHONPATH变得十分臃肿
- 如果你使用sys.path,由于文件夹是动态添加的,所以当你使用相对路径的时候,实际路径会十分依赖于你的入口函数,当入口函数改变很可能就会导致代码无法运行
- 如果你使用绝对路径,将你的代码在其他机器上运行的时候需要重新配置这些变量,十分麻烦
import * 与 __all__
当你在package项目中执行 from subpackage1 import * 会发生什么呢 ?
理想情况下,我们希望程序会在指定的subpackage1包文件中,找到所有子模块, 并把它们全部导入. 那么真的是这样吗?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
[root@happy_test_hostname demo-python-project]# pwd /root/demo-python-project [root@happy_test_hostname demo-python-project]# [root@happy_test_hostname demo-python-project]# cat package/subpackage1/foo1.py #!/usr/bin/python # -*- coding: UTF-8 -*- kobe="123" def speak(): print("This is foo1 speaking, I'm from pakcage.subpackage1") if __name__ == "__main__": speak() [root@happy_test_hostname demo-python-project]# [root@happy_test_hostname demo-python-project]# python Python 3.4.1 (default, May 5 2017, 01:45:53) [GCC 4.4.7 20120313 (Red Hat 4.4.7-17)] on linux Type "help", "copyright", "credits" or "license" for more information. >>> from package.subpackage1 import * # 使用这种方式导入 >>> >>> >>> dir() ['__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__'] >>> >>> >>> print(foo1.kobe) # 结果发现foo1模块并没有被导入 Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name 'foo1' is not defined >>> >>> from package.subpackage1 import foo1 # 直接指定模块导入,那肯定可以 >>> print(foo1.kobe) 123 >>> dir() ['__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', <span style="color: #000000;">'foo1'</span>] >>> >>> exit() [root@happy_test_hostname demo-python-project]# |
由上面的测试我们看到, import * 貌似并不会向我们期待的那样将对应包下面对应的模块全部导入。
这里一定要注意不要和模块的导入混为一谈,我们上面测试的包的导入,from package.subpackage1 import * 这个import语句中import的对象是一个包,如果是一个模块,那是可以直接调用模块里的内容的,比如:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
[root@happy_test_hostname demo-python-project]# python Python 3.4.1 (default, May 5 2017, 01:45:53) [GCC 4.4.7 20120313 (Red Hat 4.4.7-17)] on linux Type "help", "copyright", "credits" or "license" for more information. >>> >>> from package.subpackage1.foo1 import * # 针对模块的 import * >>> >>> speak() This is foo1 speaking, I'm from pakcage.subpackage1 >>> print(kobe) 123 >>> >>> |
哦,终于明白了,那么如果我就想让 import * 也可以导入包中的模块怎么办呢 ? 当然也是有办法的,就利用我们的__all__ 变量,只需要在对应包的的 __init__.py
代码定义了一个名为 __all__
的列表,那么当遇到 from package import *
时, 这个 __all__ 就被用来作为导入的模块名字的列表。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
[root@happy_test_hostname demo-python-project]# cat package/subpackage1/__init__.py __all__ = ["foo1"] [root@happy_test_hostname demo-python-project]# [root@happy_test_hostname demo-python-project]# python Python 3.4.1 (default, May 5 2017, 01:45:53) [GCC 4.4.7 20120313 (Red Hat 4.4.7-17)] on linux Type "help", "copyright", "credits" or "license" for more information. >>> >>> from package.subpackage1 import * # 即使我用 import * 也可以导入包的foo1模块啦! >>> >>> print(foo1.kobe) 123 >>> >>> exit() |
但是这样可能有点麻烦的是你需要不断的维护这个__all__列表,所以个人建议,在使用 import 时,最好能指定具体的模块,而尽量避免使用 import * 这种用法。
参考
https://docs.python.org/3/tutorial/modules.html
http://blog.csdn.net/luo123n/article/details/49849649