Python3 - 理解 Python 模块(import) 原理

Python3 - 理解 Python 模块(import) 原理

在我们的 Python 文件中使用 import 语句非常常见。即使对于有经验的 Pythonista 使用者,导入也可能会造成混淆,因为没有单一的方法可以确保导入始终有效。

本文的目的是了解导入声明如何工作的内部原理,以便加深了解并解决常见的导入问题:

  • 我们如何创建 Python 模块
  • Python 解释器搜索模块的路径
  • 如何使用import语句获得对模块中定义的对象的访问

Python 模块:概述

Python 模块是具有.py扩展名的文件。 Python 模块非常易于构建。我们需要做的就是创建一个包含合法 Python 代码的文件,并为文件命名以.py扩展名。而已!!

模块是 Python 中促进代码模块化的构造。利用模块可以使我们在利用现有代码的同时使程序更强大,更强大。

例如,假设我们创建一个名为”mod.py”的模块,其中包含以下代码:

1
2
3
4
5
6
name = "Sarah"
age = 26
def greet():
    print("Welcome {}!!".format(name))
class xyz:
    pass

可以通过如下导入模块来访问模块 mod.py 中的对象:

1
2
3
4
5
6
7
8
9
10
>>> import mod
>>> print(mod.name)
Sarah
>>> mod.age
26
>>> mod.greet()
'Welcome Sarah!!'
>>> obj = mod.xyz()
>>> obj
<mod.xyz object at 0x10e2173d0>

模块路径解析

Python 执行以下语句会发生什么:

1
import mod

当解释器执行上面的import语句时,它将在从以下来源收集的目录列表中搜索mod.py

  • 输入脚本的运行目录,如果解释器正在交互运行,则为当前目录。
  • PYTHONPATH环境变量(如果已设置)中包含的目录列表。 (“PYTHONPATH”的格式取决于操作系统,但与”PATH”环境变量相似。)
  • 在安装 Python 时配置的与安装有关的目录列表。

在 Python 变量 sys.path 中可以访问生成的搜索路径,该变量是从名为 sys 的模块获得的:

1
2
3
4
5
6
>>> import sys
>>> sys.path
['', '/home/roark/personal', '/home/roark/workdir',
'/usr/local/Python/3.7/lib',
'/usr/local/python3.7/site-packages',
'/usr/local/Python/3.7/python37.zip', '/usr/local/Python/3.7/lib/python3.7']

因此,为了确保找到我们的模块,我们需要执行以下操作之一:

  • mod.py放置在输入脚本所在的目录或当前工作目录中(如果是交互式的话)
  • 在启动解释器之前修改”PYTHONPATH”环境变量以包含”mod.py”所在的目录(或将”mod.py”放入”PYTHONPATH”变量已包含的目录之一)

实际上还有一个附加选项:我们可以将模块文件放在您选择的任何目录中,然后在运行时修改 sys.path 使其包含该目录。例如,在这种情况下,我们可以将”mod.py”放在目录”/home/sarah/”中,然后发出以下语句:

1
2
3
4
5
6
7
8
>>> sys.path.append(r'/home/sarah/')
>>> sys.path
['', '/home/roark/personal', '/home/roark/workdir',
'/home/sarah',
'/usr/local/Python/3.7/lib',
'/usr/local/python3.7/site-packages',
'/usr/local/Python/3.7/python37.zip', '/usr/local/Python/3.7/lib/python3.7']
>>> import mod

导入模块后,我们可以使用模块的file属性来确定找到该模块的位置:

1
2
3
4
5
6
7
>>> import mod
>>> mod.__file__
'/home/sarah/mod.py'

>>> import datetime
>>> datetime.__file__
'/usr/local/Python/3.7/lib/datetime.py'

file的目录部分应该是 sys.path 中的目录之一。

导入方式分析

模块内容可以通过import语句提供给调用者。 import 语句采用多种不同形式,如下所示。

  1. import <模块名称>

最简单的形式就是我们已经在上面看到的形式。 import <模块名称>

请注意,它不会使调用者可以直接访问模块内容。每个模块都有其自己的“专用符号表”,它用作_中定义的所有对象的全局符号表。因此,如前所述,模块将创建一个单独的名称空间

从调用者处,模块中的对象只有在以<模块名称>为前缀并通过 **'.'** 进行访问时才可访问,如下所示。

在”import”语句之后,将”mod”放入本地符号表。因此,”mod”在调用者的本地上下文中具有含义:

1
2
3
>>>import mod
mod
<module 'mod' from '/Users/z003fxh/personal/mod.py'>

语句 import <模块名称>仅将<模块名称>放在调用者的符号表中。在模块中定义的对象保留在模块的专用符号符号表中。

因此,“age”,“name”,“greet”和”xyz”保留在模块的专用符号表中,在调用者的上下文中没有意义。要在调用者的上下文中访问,模块中定义的对象名称必须以mod开头:

1
2
3
4
5
6
>>> greet()
NameError: name greet is not defined
>>> mod.greet()
'Welcome Sarah!!'
>>> mode.age
26
  1. form <模块名称> import <名称>

import 语句的另一种形式允许将模块中的各个对象直接导入到调用者的符号表中。 form <模块名称> import <名称>

使用此语法,可以在调用者的环境中引用”<名称>",而无需使用"<模块名称>"前缀:

1
2
3
4
5
6
7
8
9
10
>>> from mod import name, greet
>>> name
'Sarah'
>>> greet()
'Welcome Sarah!!'

>>> from mod import xyz
>>> obj = xyz()
>>> obj
<mod.xyz object at 0x102d46b90>

由于这种形式的“导入”将对象名称直接放置在调用者的符号表中,因此任何已经存在的具有相同名称的对象都将被“覆盖”:

1
2
3
4
5
6
7
>>> name = 'Roark'
>>> age = 25
>>> name, age
('Roark', 25)
>>> from mod import name, age
>>> name, age
('Sarah', 26)
  1. from <模块名称> import <名称> as <别名>

也可以“导入”单个对象,然后使用备用名称将其输入到本地符号表中。 from <模块名称> import <名称> as <别名>

这样就可以将名称直接放置在本地符号表中,但可以避免与以前存在的名称冲突:

1
2
3
4
5
6
7
8
9
10
11
12
>>> name = 'Roark'
>>> age = 25

>>> from mod import name as k_name, age as k_age
>>> name
'Roark'
>>> k_name
'Sarah'
>>> age
25
>>> k_age
26
  1. import <模块名称> as <别名>>

我们还可以使用备用名称导入整个模块: import <模块名称> as <别名>

1
2
3
4
5
>>> import mod as my_module
>>> my_module.age
26
>>> my_module.greet()
'Welcome Sarah!!'

最后,带有ImportError子句的try语句可以用来防止不成功的import尝试:

1
2
3
4
5
6
7
8
>>> try:
...     # Non-existent module
...     import def
... except ImportError:
...     print('Module not found')
...

Module not found

以上在python3.7中测试通过。

Rating: