官方手册: https://docs.python.org/3/tutorial/modules.html
可执行文件和模块
python 源代码文件按照功能可以分为两种类型:
用于执行的可执行程序文件
不用与执行, 仅用于被其它 python 源码文件导入的模块文件
例如文件 a.py 和 b.py 在同一目录下, 它们的内容分别是:
- # b.py
- x="var x in module b"
- y=5
- # a.py:
- import b
- import sys
- print(b.x)
- print(b.y)
a.py 导入其它文件 (b.py) 后, 就可以使用 b.py 文件中的属性(如变量, 函数等). 这里, a.py 就是可执行文件, b.py 就是模块文件, 但模块名为 b, 而非 b.py.
python 提供了一些标准库, 是预定义好的模块文件, 例如上面的 sys 模块.
在此有几个注意点, 在后面会详细解释:
模块 b 的文件名为 b.py, 但 import 导入的时候, 使用的名称为 b, 而非 b.py
a.py 和 b.py 是在同一个目录下的, 如果不在同目录下能否导入?
在 a.py 中访问 b.py 模块中的属性时, 使用的是 b.x,b.y
上面都是直接以模块名导入的, python 还支持更复杂的包导入方式, 例如导入 abc/b.py 时, 使用 import abc.b. 下一篇文章会详细解释包的导入方式
python 模块搜索路径
在 a.py 中导入模块 b 的时候, python 会做一系列的模块文件路径搜索操作: b.py 在哪里? 只有找到它才能读取, 运行 (装载) 该模块.
在任何一个 python 程序启动时, 都会将模块的搜索路径收集到 sys 模块的 path 属性中(sys.path). 当 python 需要搜索模块文件在何处时, 首先搜索内置模块, 如果不是内置模块, 则搜索 sys.path 中的路径列表, 搜索时会从该属性列出的路径中按照从前向后的顺序进行搜索, 并且只要找到就立即停止搜索该模块文件(也就是说不会后搜索的同名模块覆盖先搜索的同名模块).
例如, 在 a.py 文件中输出一下这个属性的内容:
- # a.py:
- import sys
- print(sys.path)
结果:
['G:\\pycode', 'C:\\Program Files (x86)\\Python36-32\\python36.zip', 'C:\\Program Files (x86)\\Python36-32\\DLLs', 'C:\\Program Files (x86)\\Python36-32\\lib', 'C:\\Program Files (x86)\\Python36-32', 'C:\\Users\\malong\\AppData\\Roaming\\Python\\Python36\\site-packages', 'C:\\Program Files (x86)\\Python36-32\\lib\\site-packages']
python 模块的搜索路径包括几个方面, 按照如下顺序搜索:
程序文件 (a.py) 所在目录, 即 G:\\pycode
环境变量 PYTHONPATH 所设置的路径(如果定义了该环境变量, 则从左向右的顺序搜索)
标准库路径
.pth 文件中定义的路径
需要注意, 上面 sys.path 的结果中, 除了. zip 是一个文件外, 其它的搜索路径全都是目录, 也就是从这些目录中搜索模块 X 的文件 X.py 是否存在.
程序所在目录
这个目录是最先搜索的, 且是 python 自动搜索的, 无需对此进行任何设置. 从交互式 python 程序终输出 sys.path 的结果:
- >>> sys.path
- ['','C:\\WINDOWS\\system32','C:\\Program Files (x86)\\Python36-32\\Lib\\idlelib','C:\\Program Files (x86)\\Python36-32\\python36.zip','C:\\Program Files (x86)\\Python36-32\\DLLs','C:\\Program Files (x86)\\Python36-32\\lib','C:\\Program Files (x86)\\Python36-32','C:\\Users\\malong\\AppData\\Roaming\\Python\\Python36\\site-packages','C:\\Program Files (x86)\\Python36-32\\lib\\site-packages']
其中第一个''表示的就是程序所在目录.
注意程序所在目录和当前目录是不同的. 例如, 在 / tmp / 目录下执行 / pycode 中的 a.py 文件
- cd /tmp
- python /pycode/a.py
其中 / tmp 为当前目录, 而 / pycode 是程序文件 a.py 所在的目录. 如果 a.py 中导入 b.py, 那么将首先搜索 / pycode, 而不是 / tmp.
环境变量 PYTHONPATH
这个变量中可以自定义一系列的模块搜索路径列表, 这样可以跨目录搜索(另一种方式是设置. pth 文件). 但默认情况下这个环境变量是未设置的.
在 Windows 下, 设置 PYTHONPATH 环境变量的方式: 命令行中输入: SystemPropertiesAdvanced-->环境变量 -->系统环境变量新建
如果是多个路径, 则使用英文格式的分号分隔. 以下是临时设置当前命令行窗口的 PYTHONPATH:
set PYTHONPATH='D:\pypath; d:\pypath1'
在 unix 下, 设置 PYTHONPATH 环境变量的方式, 使用冒号分隔多个路径:
PYTHONPATH=/tmp/pypath1:/tmp/pypath2
如果要永久生效, 则写入配置文件中:
- echo 'export PYTHONPATH=/tmp/pypath1:/tmp/pypath2'>/etc/profile.d/pypth.sh
- chmod +x /etc/profile.d/pypth.sh
- source /etc/profile.d/pypth.sh
标准库路径
在 Linux 下, 标准库的路径一般是在 / usr/lib/pythonXXX / 下(XXX 表示 python 版本号), 此目录下有些分了子目录.
例如:
['','/usr/lib/python35.zip','/usr/lib/python3.5','/usr/lib/python3.5/plat-x86_64-linux-gnu','/usr/lib/python3.5/lib-dynload','/usr/local/lib/python3.5/dist-packages','/usr/lib/python3/dist-packages']
其中 / usr/lib/python3.5 和其内的几个子目录都是标准库的搜索路径.
注意其中 / usr/lib/python35.zip, 它是 ZIP 文件组件, 当定义此文件为搜索路径时, 将自动解压缩该文件, 并从此文件中搜索模块.
Windows 下根据 python 安装位置的不同, 标准库的路径不同. 如果以默认路径方式安装的 python, 则标准库路径为 C:\\Program Files (x86)\\Python36-32 及其分类的子目录.
.pth 文件自定义路径
可以将自定义的搜索路径放进一个. pth 文件中, 每行一个搜索路径. 然后将. pth 文件放在 python 安装目录或某个标准库路径内的 sitepackages 目录下即可.
这是一种替换 PYTHONPATH 的友好方式, 因为不同操作系统设置环境变量的方式不一样, 而以文件的方式记录是所有操作系统都通用的.
例如, Windows 下, 在 python 安装目录 C:\\Program Files (x86)\\Python36-32 下新增一个 mypath.pth 文件, 内容如下:
- d:\pypath1
- d:\pypath2
再去输出 sys.path, 将可以看到这两个路径已经放进了搜索列表中.
修改搜索路径
除了上面环境变量和. pth 文件, 还可以直接修改 sys.path 或者 site.getsitepackages()的结果.
例如, 在 import 导入 sys 模块之后, 可以修改 sys.path, 向这个列表中添加其它搜索路径, 这样之后导入其它模块的时候, 也会搜索该路径.
例如:
- import sys
- sys.path.append('d:\\pypath3')
- print(sys.path)
sys.path 的最后一项将是新添加的路径.
导入模块的细节
导入模块时的过程
python 的 import 是在程序运行期间执行的, 并非像其它很多语言一样是在编译期间执行. 也就是说, import 可以出现在任何地方, 只有执行到这个 import 行时, 才会执行导入操作. 且在 import 某个模块之前, 无法访问这个模块的属性.
python 在 import 导入模块时, 首先搜索模块的路径, 然后编译并执行这个模块文件. 虽然概括起来只有两个过程, 但实际上很复杂.
前文已经解释了 import 的模块搜索过程, 所以这里大概介绍 import 的其它细节.
以前面的 a.py 中导入模块文件 b.py 为例:
import b
import 导入模块时, 搜索到模块文件 b.py 后:
1. 首先在内存中为每个待导入的模块构建 module 类的实例: 模块对象. 这个模块对象目前是空对象, 这个对象的名称为全局变量 b.
注意细节: module 类的对象, 变量 b.
输出下它们就知道:
- print(b)
- print(type(b))
输出结果:
- <module 'b' from 'g:\\pycode\\b.py'>
- <class 'module'>
因为 b 是全局变量, 所以当前程序文件 a.py 中不能重新对全局变量 b 进行赋值, 这会使导入的模块 b 被丢弃. 例如, 下面是错误的:
- import b
- b=3
- print(b.x) # 已经没有模块 b 了
另外, 因为 import 导入时是将模块对象赋值给模块变量, 所以模块变量名不能是 python 中的一些关键字, 比如 if,for 等, 这时会报错. 虽然模块文件名可以为 list,keys 等这样的内置函数名, 但这会导致这些内置函数不可用, 因为根据变量查找的作用域规则, 首先查找全局变量, 再查找内置作用域. 也就是说, 模块文件的文件名不能是这些关键字, 也不应该是这些内置函数名.
- File "g:/pycode/new.py", line 11
- import if
- ^
- SyntaxError: invalid syntax
2. 构造空模块实例后, 将编译, 执行模块文件 b.py, 并按照一定的规则将一些结果放进这个模块对象中.
注意细节, 编译, 执行 b.py, 将结果保存到模块对象中.
模块第一次被导入的时候, 会进行编译, 并生成. pyc 字节码文件, 然后 python 执行这个 pyc 文件. 当模块被再次导入时, 如果检查到 pyc 文件的存在, 且和源代码文件的上一次修改时间戳 mtime 完全对应(也就是说, 编译后源代码没有进行过修改), 则直接装载这个 pyc 文件并执行, 不会再进行额外的编译过程. 当然, 如果修改过源代码, 将会重新编译得到新的 pyc 文件.
注意, 并非所有的 py 文件都会生成编译得到的 pyc 文件, 对于那些只执行一次的程序文件, 会将内存中的编译结果在执行完成后直接丢弃(多数时候如此, 但仍有例外, 比如使用模块可以强制编译成 pyc 文件), 但模块会将内存中的编译结果持久化到 pyc 文件中. 另外, 运行字节码 pyc 文件并不会比直接运行 py 文件更快, 执行它也一样是一行行地解释, 执行, 唯一快的地方在于导入装载的时候无需重新编译而已.
执行模块文件 (已完成编译) 的时候, 按照一般的执行流程执行: 一行一行地, 以代码块为单元执行. 一般地, 模块文件中只用来声明变量, 函数等属性, 以便提供给导入它的模块使用, 而不应该有其他任何操作性的行为, 比如 print()操作不应该出现在模块文件中, 但这并非强制.
总之, 执行完模块文件后, 这个模块文件将有一个自己的全局名称空间, 在此模块文件中定义的变量, 函数等属性, 都会记录在此名称空间中.
最后, 模块的这些属性都会保存到模块对象中. 由于这个模块对象赋值给了模块变量 b, 所以通过变量 b 可以访问到这个对象中的属性(比如变量, 函数等), 也就是模块文件内定义的全局属性.
只导入一次
假设 a.py 中导入了模块 b 和模块 sys, 在 b.py 中也导入了模块 sys, 但 python 默认对某个模块只会导入一次, 如果 a.py 中先导入 sys, 再导入 b, 那么导入 b 并执行 b.py 的时候, 会发现 sys 已经导入了, 不会再去导入 sys.
实际上, python 执行程序的时候, 会将所有已经导入的模块放进 sys.module 属性中, 这是一个 dict, 可以通过下面的方式查看已导入的模块名:
- >>> import sys
- >>> list(sys.module.keys())
如果某个程序文件中多次使用 import(或 from)导入同一个模块, 虽然不会报错, 但实际上还是直接使用内存中已装载好的模块对象.
例如, b.py 中 x=3, 导入它之后修改该值, 然后再次导入, 发现 b.x 并不会发生改变:
- import b
- print(b.x) # 3
- b.x=33
- print(b.x) # 33
- import b
- print(b.x) # 33
但是 python 提供了 reload 进行多次重复导入的方法, 见后文.
使用别名
import 导入时, 可以使用 as 关键字指定一个别名作为模块对象的变量, 例如:
- import b as bb
- bb.x=3
- print(bb.x)
这时候模块对象将赋值给变量 bb, 而不是 b,b 此时不再是模块对象变量, 而仅仅只是模块名. 使用别名并不会影响性能, 因为它仅仅只是一个赋值过程, 只不过是从原来的赋值对象变量 b 变为变量 bb 而已.
from 导入部分属性
import 语句是导入模块中的所有属性, 并且访问时需要使用模块变量来引用. 例如:
- import b
- print(b.x)
除了 import, 还有一个 from 语句, 表示从模块中导入部分指定的属性, 且使得可以直接使用这些属性的名称来引用这些属性, 而不需要加上模块变量名. 例如原来 import 导入时访问变量 x 使用 b.x,from 导入时只需使用 x 即可. 实际上, from 导入更应该称为属性的再次赋值(拷贝).
例如, b.py 中定义了变量 x,y,z, 同时定义了函数 f()和 g(), 在 a.py 中导入这个模块文件, 但只导入 x 变量和 f 函数:
- # a.py 文件内容:
- from b import x,f
- print(x)
- f()
- # b.py 文件内容:
- x=3
- y=4
- z=5
- def f():
- print("function f in b.py")
- def g():
- print("function g in b.py")
注意上面 a.py 中引用模块 b 中属性的方式没有加上 b.X, 而是直接使用 x 和 f()来引用. 这和 import 是不一样的. 至于 from 和 import 导入时的变量名称细节, 在下面的内容中会详细解释.
虽然 from 语句只导入模块的部分属性, 但实际上仍然会完整地执行整个模块文件.
同样的, from 语句也可以指定导入属性的变量别名, 例如, 将 b.py 中的属性 x 赋值给 xx, 将 y 赋值给 yy:
- from b import x as xx,y as yy
- print(xx)
- print(yy)
from 语句还有一个特殊导入统配符号 *, 它表示导入模块中的所有属性.
- # a.py 文件:
- from b import *
- print(x,y,z)
- f()
- g()
多数时候, 不应该使用 from * 的方式, 因为我们可能会忘记某个模块中有哪些属性拷贝到了当前文件, 特别是多个 from * 时可能会出现属性覆盖的问题.
重载模块: imp.reload()
无论时 import 还是 from, 都只导入一次模块, 但使用 reload()可以强制重新装载模块.
reload()是 imp 模块中的一个函数, 所以要使用 imp.reload()之前, 必须先导入 imp.
- from imp import reload
- reload(b)
reload()是一个函数, 它的参数是一个已经成功被导入过的模块变量(如果使用了别名, 则应该使用别名作为 reload 的参数), 也就是说该模块必须在内存中已经有自己的模块对象.
reload()会重新执行模块文件, 并将执行得到的属性完全覆盖到原有的模块对象中. 也就是说, reload()会重新执行模块文件, 但不会在内存中建立新的模块对象, 所以原有模块对象中的属性可能会被修改.
例如, 模块文件 b.py 中 x=3, 导入 b 模块, 修改其值为 33, 然后 reload 这个模块, 会发现值重新变回了 3.
- import b
- print(b.x) # 3
- b.x=33
- print(b.x) # 33
- from imp import reload
- reload(b)
- print(b.x) # 3
有时候 reload()很有用, 可以让程序无需重启就执行新的代码. 例如, 在 python 的交互式模式下导入模块 b, 然后修改 python 源码, 再 reload 导入:
- >>> import b
- >>> b.x
- 3
- # 不要关掉交互式解释器, 直接修改源代码中的 b=3333
- >>> from imp import reload
- >>> reload(b)
- <module 'b' from 'G:\\pycode\\b.py'>
- >>> b.x
- 3333
但正因为 reload()重载模块会改变原始的值, 这可能是很危险的行为, 一定要清楚地知道它是在干什么.
导入模块时的变量名称细节
import 导入的变量
import 导入时, 模块对象中的属性有自己的名称空间, 然后将整个模块对象赋值给模块变量.
例如, 在 a.py 中导入 b:
- import b
- print(b.x)
这个过程唯一和当前文件 a.py 作用域有关的就是模块对象变量 b,b.py 中声明的属性和当前文件无任何关系. 无论是访问还是修改, 都是直接修改这个模块对象自身作用域中的值. 所以, 只要模块变量 b 不出现冲突问题, 可以放心地修改模块 b 中的属性.
另一方面, 因为每个进程都有自己的内存空间, 所以在 a.py,c.py 中都导入 b 时, a.py 中修改 b 的属性值不会影响 c.py 中导入的属性, a.py 和 c.py 中模块对象所保存的属性都是执行 b.py 后得到的, 它们相互独立.
from 导入的变量
from 导入模块时, 会先执行完模块文件, 然后将指定的部分属性重新赋值给当前程序文件的同名全局变量.
例如, 在模块文件 b.py 中定义了 x,y,z 变量和 f(),g()函数:
- # b.py:
- x=3
- y=4
- b=5
- def f():
- print("function f in b.py")
- def g():
- print("function g in b.py")
当在 a.py 中导入 b 模块时, 如果只导入 x,y 和 f():
- # a.py:
- from b import x, y, f
实际上的行为是构造模块对象后, 将这个模块对象对应的名称空间中的属性 x,y 和 f 重新赋值给 a.py 中的变量 x,y 和 f, 然后丢弃整个模块对象以及整个名称空间. 换句话说, b 不再是一个有效的模块变量(所以和 import 不一样), 来自 b 的 x,y,z,f 和 g 也都被丢弃.
这里有几个细节, 需要详细解释清楚, 只有理解了才能搞清楚它们是怎么生效的.
假设现在模块文件 b.py 的内容为, 并且 a.py 中导入 x,y,f 属性:
- # b.py:
- x=3
- y=[1,2]
- z=5
- def f():
- print("function f in b.py")
- def g():
- print("function g in b.py")
- # a.py:
- from b import x,y,f
首先在执行模块文件 b.py 时, 会构造好自己的模块对象, 并且模块对象有自己的名称空间(作用域), 模块对象构造完成后, 它的名称空间大致如下:
然后 python 会在 a.py 的全局作用域内创建和导入属性同名的全局变量 x,y 和 f, 并且通过赋值的方式将模块的属性赋值给这些全局变量, 也就是:
- x = b.x
- y = b.y
- f = b.f
上面的 b 只是用来演示, 实际上变量 b 是不存在的.
赋值完成后, 我们和构造的整个模块对象就失去联系了, 因为没有变量 b 去引用这个对象. 但需要注意, 这个对象并没有被删除, 仅仅只是我们无法通过 b 去找到它.
所以, 现在的示意图如下:
因为是赋值的方式传值的, 所以在 a.py 中修改这几个变量的值时, 是直接在模块对象作用域内修改的: 对于不可变对象, 将在此作用域内创建新对象, 对于可变对象, 将直接修改原始对象的值.
另一方面, 由于模块对象一直保留在内存中, 下次继续导入时, 将直接使用该模块对象. 对于 import 和 from, 是直接使用该已存在的模块对象, 对于 reload, 是覆盖此模块对象.
例如, 在 a.py 中修改不可变对象 x 和可变对象 y, 之后 import 或 from 时, 可变对象的值都会随之改变, 因为它们使用的都是原来的模块对象:
- from b import x,y
- x=33
- y[0]=333
- from b import x,y
- print((x,y)) # 输出(3, [333, 2])
- import b
- print((b.x,b.y)) # 输出(3, [333, 2])
from 导入时, 由于 b 不再是模块变量, 所以无法再使用 reload(b)去重载对象. 如果想要重载, 只能先 import, 再 reload:
- from b import x,y
- ...CODE...
- # 想要重载 b
- import b
- from imp import reload
- reload(b)
查看模块中的属性
内置函数 dir 可用于列出某模块中定义了哪些属性.
import b dir(b)
输出结果:
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'f', 'g', 'x', 'y', 'z']
可见, 模块的属性中除了自己定义的属性外, 还有一些内置的属性, 比如上面以__开头和结尾的属性.
如果 dir()不给任何参数, 则输出当前环境下定义的名称属性:
- >>> import b
- >>> x=3
- >>> aaa=333
- >>> dir()
- ['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'aaa', 'b', 'x']
每个属性都对应一个对象, 例如 x 对应的是 int 对象, b 对应的是 module 对象:
- >>> type(x)
- <class 'int'>
- >>> type(b)
- <class 'module'>
既然是对象, 那么它们都会有自己的属性. 例如:
- >>> dir(x)
- ['__abs__', '__add__', '__and__', '__bool__', '__ceil__', '__class__', '__delattr__', '__dir__', '__divmod__', '__doc__', '__eq__', '__float__', '__floor__', '__floordiv__', '__format__', '__ge__', '__getattribute__', '__getnewargs__', '__gt__', '__hash__', '__index__', '__init__', '__init_subclass__', '__int__', '__invert__', '__le__', '__lshift__', '__lt__', '__mod__', '__mul__', '__ne__', '__neg__', '__new__', '__or__', '__pos__', '__pow__', '__radd__', '__rand__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__', '__round__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__trunc__', '__xor__', 'bit_length', 'conjugate', 'denominator', 'from_bytes', 'imag', 'numerator', 'real', 'to_bytes']
所以, 也可以直接 dir 某个模块内的属性:
- import b
- dir(b.x)
- dir(b.__name__)
dir()不会列出内置的函数和变量, 如果想要输出内置的函数和变量, 可以去标准模块 builtins 中查看, 因为它们定义在此模块中:
- import builtins
- dir(buildins)
来源: https://www.cnblogs.com/f-ck-need-u/p/9955485.html