【转】python模块导入细节

python模块导入细节

官方手册:https://docs.python.org/3/tutorial/modules.html

可执行文件和模块

python源代码文件按照功能可以分为两种类型:

  1. 用于执行的可执行程序文件
  2. 不用与执行,仅用于被其它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模块。

在此有几个注意点,在后面会详细解释:

  1. 模块b的文件名为b.py,但import导入的时候,使用的名称为b,而非b.py
  2. a.py和b.py是在同一个目录下的,如果不在同目录下能否导入?
  3. 在a.py中访问b.py模块中的属性时,使用的是b.xb.y
  4. 上面都是直接以模块名导入的,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文件,对于那些只执行一次的程序文件,会将内存中的编译结果在执行完成后直接丢弃(多数时候如此,但仍有例外,比如使用compileall模块可以强制编译成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)

【转】python模块导入细节的更多相关文章

  1. python模块导入细节

    python模块导入细节 官方手册:https://docs.python.org/3/tutorial/modules.html 可执行文件和模块 python源代码文件按照功能可以分为两种类型: ...

  2. python包导入细节

    包导入格式 导入模块时除了使用模块名进行导入,还可以使用目录名进行导入.例如,在sys.path路径下,有一个dir1/dir2/mod.py模块,那么在任意位置处都可以使用下面这种方式导入这个模块. ...

  3. 【转】python包导入细节

    [转]python包导入细节 包导入格式 导入模块时除了使用模块名进行导入,还可以使用目录名进行导入.例如,在sys.path路径下,有一个dir1/dir2/mod.py模块,那么在任意位置处都可以 ...

  4. 一文解决python模块导入

    python 模块导入 原理 查找是按照 sys.path 中的路径挨个扫描.若都不存在则提示error. sys.path路径第一个是当前运行脚本所在的目录,其后是PYTHONPATH(一般若步专门 ...

  5. 详解Python模块导入方法

    python常被昵称为胶水语言,它能很轻松的把用其他语言制作的各种模块(尤其是C/C++)轻松联结在一起.python包含子目录中的模块方法比较简单,关键是能够在sys.path里面找到通向模块文件的 ...

  6. python模块导入总结

    python模块导入总结 模块导入方式 定义test.py模块 def print_func(): print("hello") import 语句 导入模块语法 import m ...

  7. python 模块导入import和import from区别

    模块就是一个.py文件,在名字空间下导入模块导入import和import from,那么python 模块导入import和import from区别是什么呢 1,import 导入模块 impor ...

  8. python模块导入

    官方手册:https://docs.python.org/3/tutorial/modules.html 可执行文件和模块 python源代码文件按照功能可以分为两种类型: 用于执行的可执行程序文件 ...

  9. python 模块导入详解

    本文不讨论 Python 的导入机制(底层实现细节),仅讨论模块与包,以及导入语句相关的概念.通常,导入模块都是使用如下语句: import ... import ... as ... from .. ...

随机推荐

  1. CodeForces1051E EXKMP + 线段树dp

    http://codeforces.com/problemset/problem/1051/E 题意:给你一个很大的数字,然后你可以把这个数字拆分成为任意多个部分,要求每一个部分的数字大小要在一个区间 ...

  2. python静态方法和类方法

    静态方法和类方法在python2.2中被引用,经典类和新式类都可以使用.同时,一对内建函数:staticmethod和classmethod被引入,用来转化类中某一方法为这两种方法之一. 静态方法: ...

  3. 云计算虚拟机技术-KVM安装

    云计算虚拟机技术-KVM安装 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 身为运维的小伙伴估计大家都清楚KVM,因为在CentOS里面KVM还算很折腾的一个软件,早期CentOS ...

  4. weblogic创建控制台启动脚本以及创建服务器

    一.创建控制台脚本 二.创建认证文件 通过上面创建的脚本进行启动的时候,会因为密码问题导致起不来,因为在startWebLogic.sh文件中,没有配置用户名和密码.而且通过上面创建的脚本,启动的时候 ...

  5. MyBatis-获取 xxxMapper

    Main 方法,mybatis 版本为 3.5.0 使用 MapperProxyFactory 创建一个 MapperProxy 的代理对象 代理对象里面包含了 DefaultSqlSession(E ...

  6. 7、JPA-映射-双向一对多

    一个用户对应多个订单,多个订单对应一个用户,不管查哪一边都可以得到另一边的信息 实体类 Customer package com.jpa.yingshe; import javax.persisten ...

  7. Web API中的模型验证

    一.模型验证的作用 在ASP.NET Web API中,我们可以使用 System.ComponentModel.DataAnnotations 命名空间中的属性为模型上的属性设置验证规则. 一个模型 ...

  8. Golang入门教程(三)beego 框架安装

    beego 是一个快速开发 Go 应用的 HTTP 框架,他可以用来快速开发 API.Web 及后端服务等各种应用,是一个 RESTful 的框架,主要设计灵感来源于 tornado.sinatra ...

  9. Spark源码剖析 - SparkContext的初始化(五)_创建任务调度器TaskScheduler

    5. 创建任务调度器TaskScheduler TaskScheduler也是SparkContext的重要组成部分,负责任务的提交,并且请求集群管理器对任务调度.TaskScheduler也可以看作 ...

  10. MDB数据类型注意事项

    在ArcGIS中,对MDB数据类型的支持较少,如下图: 当时当你打开Access软件的时候,你会发现文本类型,其实有很多种: 假如涉及到的字段类型是文本,而且又比较长,最好设置为“备注”