模块和包
在 Python 中每一个 .py
都可以视作一个模块(module),而每一个包含 __init__.py
的目录则可以视作包(packge)。
如下所示,packs 因为包含 __init__.py
而可以看做是一个包,而 packs/one.py
则是一个模块。
1 2 3 4 5 6 7
| ├── main.py ├── packs │ ├── __init__.py │ └── one.py └── readme.md
1 directory, 4 files
|
在 one.py 中简单定义了一个函数 One:
1 2 3 4 5 6
| def One(): print("module one")
def OneOne(): print("module one/one")
|
如果我们想要在 main.py 中使用的话,那么可以直接 import,这种方式也称之为“模块导入”。
1 2 3
| import packs
packs.one.One()
|
这里使用的同时加上了模块名称,当然也可以省略模块名,而这种方式称之为“成员导入”,这种方式比之前一种方式要快一些。
1 2 3
| from packs.one import One
One()
|
当我们运行后就会打印 module one
,另外也可以看到在 packs 目录下生成一个 __pycache__
的新目录,这是编译后中间文件,可以提升模块载入速度。
如果当前模块有多个 One
名称导入的话,可以使用别名进行区分。如果有多个导入名称的话可以使用逗号进行分隔。
1 2 3 4
| from packs.one import One as Alias, OneOne
Alias() OneOne()
|
如果要在一行导入的名称过多,也可以分行写
1 2 3 4 5
| from packs.one import One from packs.one import OneOne
One() OneOne()
|
当然也可以全部导入到当前模块,不过注意这种方式可能存在命名冲突,并且在一些 linter 工具下会提示必须使用全部的导入名称。
1 2 3 4
| from packs.one import *
One() OneOne()
|
除了这种全局导入外,还可以在局部作用域导入。
1 2 3 4 5
| def main(): from packs.one import One One()
main()
|
init 文件
这里的 __init__.py
每当导入当前包的模块的时候就会运行一次。
现在在 packs/__init__.py
中加入一行 print("packs one imported")
的语句,然后运行,可以发现 __init__.py
是首先运行的。
1 2 3
| $ python3 main.py packs one imported module one
|
根据这个特点,我们可以再 __init__.py
输入导出的模块,外部使用的就不需要很长的导入路径了。
修改 packs/__init__py
为如下所示:
1 2 3
| from .one import One
print("packs one imported")
|
那么在 main.py
中就可以这样使用:
1 2 3
| from packs import One
One()
|
或者这样子:
1 2 3
| import packs
packs.One()
|
在深入一些,我们在 packs
文件夹下新建一个 two
包,然后修改 main.py
并导入这个包,类如下面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| $ tree . . ├── main.py ├── packs │ ├── __init__.py │ ├── one.py │ └── two │ ├── __init__.py │ └── two.py └── readme.md
2 directories, 6 files
$ cat packs/two/__init__.py print("packs two imported")
$ cat packs/two/two.py def Two(): print("module two")
$ cat main.py from packs.two.two import Two
Two()
|
接着我们运行 main.py
1 2 3
| packs one imported packs two imported module two
|
可以看到两个 __init__
都被运行了,但是我们还不能使用 packs.one.One
这个函数,因为在 main.py
并没有导入这个名称。
相对路径和绝对路径引用
上面有使用类似这种带有相对路径的导入路径 from .XXX
,这种代表从当前的 XXX
模块中导入名称。如果想要在 packs/two/two.py
中使用上一层的 packs/one.py
就可以使用 from ..one import One
的方式。
1 2 3 4
| from ..one import One
One()
|
以此类推,那么 ...
代表更上一级。
但是这种方式还是有问题的,如果项目深度太大就容易写太多 .
,还有一种方式就是绝对路径引用,这里的绝对路径是指相对项目根目录而言的,比如上述例子,那么就要修改为:
1 2 3 4
| from packs.one import One
One()
|
那引用当前目录的模块必须使用相对路径了,比如上述例子:
1 2 3 4 5
| from packs.one import One from .two import Two
One()
|
注意,这里不能是 from two import Two
的形式!这个也好理解,因为绝对路径不是 .
开始的,如果相对路径不使用 .
开始,那么就得从项目根目录开始找了。
当然绝对路径的模块,就有一个 base 路径,所有的文件都是相对此 base 目录,如果在 IDE 中直接打开这里的模块,模块的根目录就是当前模块,显然就会提示找不到了对应的模块了。
模块搜索顺序
自己写的包名肯定可能和第三方或者标准库同名,不过这种同名通常没有问题。因为 python 会优先在当前目录搜索然后在环境变量的搜索路径,之后才是标准库和第三方包。
这个和 linux $PATH 的环境变量一样,按照顺序来搜索。一旦导入每个模块就有全局的命名空间,第二次再次加载就会使用缓存。
这个路径搜索方式和 nodejs 有些区别,nodejs 是一旦同名,优先标准库,如果自定义一个 http 模块,那么永远不会被加载。
pip 包管理工具
这一块比较简单,不过我感觉自己用的也不是特别熟练,这里先写写一般常用的操作。
包管理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| # 安装 # 最新版本 pip install Django # 指定版本号 pip install Django==2.0.0 # 最小版本 pip install 'Django>=2.0.0'
# 升级包 pip install --upgrade Django # 卸载包 pip uninstall SomePackage # 搜索包 pip search SomePackage # 显示安装包信息 pip show # 查看指定包的详细信息 pip show -f SomePackage # 列出已安装的包 pip list # 查看可升级的包 pip list -o
|
包依赖锁
1 2 3
| pip freeze > requirement.txt # 锁版本 pip install -r requirement.txt # 指定安装版本 pip install --user install black # 安装到用户级目录
|
使用镜像
1 2 3
| pip install -r requirements.txt \ --index-url=https://mirrors.aliyun.com/pypi/simple/ \ --extra-index-url https://pypi.mirrors.ustc.edu.cn/simple/
|
升级 pip
pip install -U pip
或者sudo easy_install --upgrade pip