Python 模块介绍,以及创建和导入 Python 模块
由于包是一种特殊的模块,本节所讲述的内容也适用于 Python 包。要想深入了解模块的相关概念,你可以查看Python 指南的Python 模块完全限定名,模块缓存介绍一节。
Python 模块
你可以简单的将 Python 脚本文件视为模块,虽然这并非得到 Python 模块的唯一方式,但却是最为常见的。Python 模块包含了一系列代码,他们可能是变量,函数或类的定义,虽然没有强制性的约束,但同一模块中的代码关联性较高。比如,模块math
拥有大量用于数学运算的函数。
如何命名 Python 模块?
在默认情况下,Python 模块的名称等同于其对应的py
脚本文件的文件名,并不需要特意的命名。比如,文件worker.py
对应的模块的名称为woker
。
Python 模块的存储形式
在项目的开发过程中,Python 模块以py
脚本文件的形式出现,如果经过了编译,模块也可以存储在pyc
字节码文件中。
下面是一个简单的模块hello
,他使用print
函数显示了一段信息。
# 模块 hello
print('这里是模块 hello!')
Python 模块的特性
在 Python 脚本文件中,你可以定义变量,函数,类等内容,这些内容即为模块的特性(Attribute),他们通过形式为m.name
,m.name=value
的语句被访问,其中m
为模块,name
为特性(名称,标识符),value
为特性的新值。
当然,模块特性可以被称呼为变量,函数或类,这并没有什么问题。
如何查看 Python 模块的所有特性?
Python 模块拥有的特殊特性__dict__
,包含了模块所有特性的信息。__dict__
本身是一个字典,字典的键是表示特性名称的字符串,字典的值是特性的具体内容。
对模块特性的访问,等同于对__dict__
键值对的访问,假设模块student
拥有特性count
,那么,student.count=100
等同于student.__dict__['count']=100
。
如何为 Python 模块添加暂时特性?
在没有限制的情况下,你可以为一个 Python 模块添加暂时特性,只需要使用正常的赋值语句m.name=value
即可。其中m
为模块,name
为特性(名称,标识符),value
为特性的值。
如何判断 Python 模块是否为程序入口点?
模块的__name__
特性表示模块的名称,该特性可在代码中修改。当一个模块被当作程序入口点时,其模块名称将被设置为'__main__'
,因此,可通过判断__name__
是否等于'__main__'
,来执行模块作为程序入口点时的特定代码。
如何获取 Python 模块对应的文件路径?
模块的__file__
特性是 Python 模块对应的文件路径,如果该模块是通过文件加载的。当模块通过非文件方式加载时,那么他可能不具备__file__
特性,此时对__file__
进行读取将导致异常AttributeError
。
# 判断 import.py 是否为程序入口点
if __name__ == '__main__':
# 显示模块的文件路径
print(__file__)
# 导入模块 teacher
import school.teacher
# 通过 __dict__ 读取 teacher 的 count 特性
print(school.teacher.__dict__['count'])
# Windows 中的输出结果
…\modules\import.py
0
Python 模块的私有特性
Python 模块有没有私有特性,是模棱两可的,你可以为 Python 特性指定以一个或更多下划线(_
)开头的名称,以表示该特性不适合被外部访问。但事实上,Python 并不会阻止此类情况的发生,通过模块访问以下划线开头的特性是被允许的。
Python 模块中以下划线(_
)开头的特性,可能不会被from…import
语句导入,这一点会在稍后给予说明。
Python 模块的文档字符串
与 Python 函数类似,模块也具有文档字符串,他被存储在 Python 模块的__doc__
特性中,以向开发人员说明模块的相关信息。文档字符串本质上是书写在模块中的字符串字面量,他必须是模块的第一行有效代码,可以采用紧密相邻或加入空白的方式进行拼接,但不应包含需要运算的内容或操作。
在模块teacher
中,我们拼接了两个字符串字面量作为模块的文档字符串。
'这里是模块 '"teacher"
# …
创建 Python 模块
当你创建一个py
脚本时,也就创建了一个 Python 模块,这是最为简单和常见的方式。除此之外,你还可以通过ModuleType
类来动态的创建模块。
模块teacher
定了自己的变量count
,level
和__avg_age
,以及函数show
,_add_avg_age
。
# …
# 模块 teacher 中的变量 count,level
count = 0
level = 1
# 模块 teacher 中的函数 show
def show():
print(f'教师的数量为 {count}')
# 使用 _ 开头,表示不适合外部访问
__avg_age = 33
def _add_avg_age(years):
# 修改模块变量 __avg_age
global __avg_age
__avg_age += years
导入 Python 模块
要在一个模块中使用另一个 Python 模块或模块的某个特性,必须先进行导入操作,通常要用到import
或from…import
语句。这两种语句的用法不同,但均会将目标绑定至当前的命名空间,其对应的标识符默认为被绑定目标的名称。
被导入的 Python 模块将作为父级包的特性
需要特别指出,当一个 Python 模块被成功导入后,除了可能被绑定至当前命名空间,还将成为其父级包的一个特性(如果父级包存在的话),特性的名称默认为模块名称。因此,如果可以在另一个命名空间中访问父级包,并且相关缓存没有失效,那么通过父级包访问作为其特性的 Python 模块将是可行的,即便你未在该命名空间中使用任何语句明确的导入该 Python 模块。
在函数或方法中导入的 Python 模块或特性无法被外部直接访问
由于拥有自己的命名空间,通过import
或from…import
语句,在函数或方法中导入的模块和特性,将通过标识符绑定至函数或方法的命名空间。基于作用域的一般性规则,这些标识符不能在函数或方法外部使用,除非在外部命名空间中重新绑定他们。
命名空间,作用域
要深入了解命名空间,你可以查看编程教程的命名空间,作用域介绍一节。
使用 import 语句导入 Python 模块
import
语句的基本格式如下,当你使用,
时,相当于执行多个import
语句,比如,import sys,os
相当于执行了import sys
和import os
。
import <module>[ as <identifier>][, …]
- module 部分
module
为需要导入模块的完全限定名,当完全限定名包含多个模块时,这些模块将被依次导入。- identifier 部分
identifier
是用于绑定操作的自定义标识符。
如何引用 import 语句导入的 Python 模块?
虽然import
语句会依次导入完全限定名中的所有模块,但在没有as
关键字的情况下,被绑定至当前命名空间的是第一个模块,而非最后一个。比如,在使用import school.teacher
导入teacher
后,依然需要通过school.teacher
来使用teacher
模块,因为命名空间中绑定的是标识符为school
的school
模块,标识符teacher
并不存在。
当import
语句包含as
关键字时,完全限定名中的最后一个模块将绑定至当前命名空间,并采用as
指定的标识符。
在书写语句import school.teacher as t
之后,你可以通过t
来使用模块teacher
,因为标识符t
将于模块teacher
绑定。
以_
开头的模块特性并没有访问上的限制,通过模块teacher
使用变量__avg_age
和函数_add_avg_age
不会有任何问题。
# 使用 as 关键字指定标识符
import school.teacher as t
# 可以通过 t 来使用 teacher 模块
t.count = 100
t.show()
# 通过模块调用所谓的私有特性
t._add_avg_age(1)
print(f'现在的平均年龄是 {t.__avg_age}')
教师的数量为 100
现在的平均年龄是 34
下面的代码,在函数中导入了模块teacher
,该模块将成为父级包school
的一个特性,特性名称为teacher
,因此,在调用函数show_teacher
后,可在函数外部通过父级包school
的teacher
特性访问teacher
模块,而不需要明确的导入。
import school
def show_teacher():
# 标识符 school 绑定在函数的命名空间中
import school.teacher
# 通过标识符 school 访问模块 teacher 的 show 函数
school.teacher.show()
# 调用函数后,teacher 模块将成为 school 的一个特性
show_teacher()
# 通过 school 的特性访问模块 teacher
print(f'Teacher 模块?{school.teacher}')
教师的数量为 0
Teacher 模块?<module 'school.teacher' from '…'>
使用 from…import 语句导入 Python 模块
与import
语句不同,from…import
语句可以使用模块的相对名称(以.
开头)或完全限定名,并且导入的目标不仅限于模块,还包括模块的特性,其语法格式如下。当你使用,
时可以导入多个目标,比如,from sys import int_info,float_info
,分别导入了sys
模块的int_info
和float_info
特性。
form <module> import <target>[ as <identifier>][, …]
- module 部分
module
是一个完全限定名或相对名称(以.
开头),用于查找需要导入的目标所在的模块或父级包,这些模块或包将被依次导入。- target 部分
target
是开发人员真正希望导入的目标,他可以指模块,也可以指模块的特性。- identifier 部分
identifier
是用于绑定操作的自定义标识符。
如何引用 from…import 语句导入的 Python 模块或特性?
在from…import
语句中,关键字import
所指示的导入目标,将被绑定至当前的命名空间,因此,他并不会像import
语句一样让你产生疑惑。比如,执行from school.teacher import show
后,你可以直接书写show()
来调用teacher
模块的show
函数。
如何使用 from…import 语句导入 Python 模块的所有特性?
在from…import
语句的import
关键字后跟随*
,即可导入相关 Python 模块的所有特性,这些特性将通过自己的名称绑定至当前命名空间。此做法仅被允许出现在模块级别,函数或方法使用*
将导致错误。
上述中所谓的“所有”并不包含以一个或更多_
开头的(私有)特性,如果需要,你可以在from…import
语句中单独的明确的导入他们。比如,from school.teacher import __avg_age
明确的导入了teacher
模块的__avg_age
特性。
不能赋值修改 from…import 语句导入的 Python 模块变量
一旦使用from…import
语句导入了 Python 模块中的变量,就可以读取该变量,但无法直接修改他,如果你试图对其进行赋值,将等同于在当前位置定义一个新的变量。
在下面的代码中,我们导入了teacher
模块的所有特性,并在当前命名空间中绑定为count
,level
和show
。语句count=100
并不会对teacher
模块的count
变量进行赋值,而是会定义新的变量count
。
此外,由于*
并不导入 Python 模块中以_
开头的特性,因此,访问teacher
模块的__avg_age
特性将导致异常NameError
。
# teacher 模块的特性 count,level,show 将被绑定至当前命名空间
from school.teacher import *
# 此处的 count 指向 teacher 模块的 count 特性
print(f'我在模块 modules 中读取的教师数量为 {count}')
show()
# 这里会定义新的变量 count,而不是对 teacher 中的 count 进行赋值
count = 100
# 调用 show 后,并不会显示 100
show()
# ERROR * 并不会导入私有特性
print(__avg_age)
我在模块 modules 中读取的教师数量为 0
教师的数量为 0
教师的数量为 0
…
NameError: name '__avg_age' is not defined
如何使用 from…import 语句导入当前 Python 模块的父级包?
在from…import
语句的target
部分中采用相对路径(以.
开头),即可导入当前 Python 模块的父级包,如果书写..
,则将导入父级包以及父级包的父级包。在有些情况下,是否书写.
均可导入同一目录中的其他模块,但由于书写.
会额外的尝试导入父级包,因此这可能导致异常ImportError
的发生,当模块没有相关父级包时。
需要指出,在 Python 包对应的__init__.py
文件的from…import
语句中,相对路径.
并不表示该包的父级包,而是表示该包自身。
在模块student
中,我们使用.
导入了同样位于school
包的guard
模块,以及teacher
模块的特性。
# 模块 student 将通过父级包导入其他模块
import school.student
# 以相对路径导入 teacher 模块的特性
from .teacher import *
print('在 student 模块中调用 show')
show()
# 以相对路径导入 guard 模块
from . import guard
print(f'守卫人员是 {guard.name}')
在 student 模块中调用 show
教师的数量为 0
守卫人员是 汪汪大队
# 模块变量 name
name = '汪汪大队'
使用 from…import * 语句不能导入 Python 包中的所有模块
使用from…import *
不能导入 Python 包中的所有模块,*
仅针对包的特性。如果在执行from…import *
语句之前,Python 包的一些子模块已经被导入,那么已导入的子模块将被from…import *
绑定至当前命名空间(因为他们是 Python 包的特性),此时可以直接书写子模块名称来使用子模块。
由于之前已经导入了teacher
模块,因此该模块是包school
的特性之一,from…import
语句会将其绑定至当前命名空间,书写teacher
没有任何问题。
另一个子模块student
并非包school
的特性,因此from…import
语句不会将student
绑定至当前命名空间。
# 由于 teacher 模块之前已经被导入,因此他是包 school 的特性
from school import *
# 这里可以使用 teacher
print(teacher)
# ERROR 访问 school 的子模块 student 会导致异常
print(student)
<module 'school.teacher' from '…'>
…
NameError: name 'student' is not defined
源码
hello.py·codebeatme/python·GitHub
import.py·codebeatme/python·GitHub
school/teacher.py·codebeatme/python·GitHub
import_attr.py·codebeatme/python·GitHub
import_from.py·codebeatme/python·GitHub
import_parent.py·codebeatme/python·GitHub
school/student.py·codebeatme/python·GitHub
school/guard.py·codebeatme/python·GitHub