Python 模块介绍,以及创建和导入 Python 模块

我被代码海扁署名-非商业-禁演绎
阅读 17:55·字数 5376·更新 
Bilibili 空间
关注 960

由于包是一种特殊的模块,本节所讲述的内容也适用于 Python 包。要想深入了解模块的相关概念,你可以查看Python 指南Python 模块完全限定名,模块缓存介绍一节。

Python 模块

你可以简单的将 Python 脚本文件视为模块,虽然这并非得到 Python 模块的唯一方式,但却是最为常见的。Python 模块包含了一系列代码,他们可能是变量,函数或类的定义,虽然没有强制性的约束,但同一模块中的代码关联性较高。比如,模块math拥有大量用于数学运算的函数。

如何命名 Python 模块?

在默认情况下,Python 模块的名称等同于其对应的py脚本文件的文件名,并不需要特意的命名。比如,文件worker.py对应的模块的名称为woker

Python 模块的存储形式

在项目的开发过程中,Python 模块以py脚本文件的形式出现,如果经过了编译,模块也可以存储在pyc字节码文件中。

下面是一个简单的模块hello,他使用print函数显示了一段信息。

hello.py
# 模块 hello
print('这里是模块 hello!')

Python 模块的特性

在 Python 脚本文件中,你可以定义变量,函数,类等内容,这些内容即为模块的特性(Attribute),他们通过形式为m.namem.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
# 判断 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中,我们拼接了两个字符串字面量作为模块的文档字符串。

school/teacher.py
'这里是模块 '"teacher"
# …

创建 Python 模块

当你创建一个py脚本时,也就创建了一个 Python 模块,这是最为简单和常见的方式。除此之外,你还可以通过ModuleType类来动态的创建模块。

模块teacher定了自己的变量countlevel__avg_age,以及函数show_add_avg_age

school/teacher.py
# …
# 模块 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 模块或模块的某个特性,必须先进行导入操作,通常要用到importfrom…import语句。这两种语句的用法不同,但均会将目标绑定至当前的命名空间,其对应的标识符默认为被绑定目标的名称。

被导入的 Python 模块将作为父级包的特性

需要特别指出,当一个 Python 模块被成功导入后,除了可能被绑定至当前命名空间,还将成为其父级包的一个特性(如果父级包存在的话),特性的名称默认为模块名称。因此,如果可以在另一个命名空间中访问父级包,并且相关缓存没有失效,那么通过父级包访问作为其特性的 Python 模块将是可行的,即便你未在该命名空间中使用任何语句明确的导入该 Python 模块。

在函数或方法中导入的 Python 模块或特性无法被外部直接访问

由于拥有自己的命名空间,通过importfrom…import语句,在函数或方法中导入的模块和特性,将通过标识符绑定至函数或方法的命名空间。基于作用域的一般性规则,这些标识符不能在函数或方法外部使用,除非在外部命名空间中重新绑定他们。

命名空间,作用域

要深入了解命名空间,你可以查看编程教程命名空间,作用域介绍一节。

使用 import 语句导入 Python 模块

import语句的基本格式如下,当你使用,时,相当于执行多个import语句,比如,import sys,os相当于执行了import sysimport os

import <module>[ as <identifier>][, …]

module 部分

module为需要导入模块的完全限定名,当完全限定名包含多个模块时,这些模块将被依次导入。

identifier 部分

identifier是用于绑定操作的自定义标识符。

如何引用 import 语句导入的 Python 模块?

虽然import语句会依次导入完全限定名中的所有模块,但在没有as关键字的情况下,被绑定至当前命名空间的是第一个模块,而非最后一个。比如,在使用import school.teacher导入teacher后,依然需要通过school.teacher来使用teacher模块,因为命名空间中绑定的是标识符为schoolschool模块,标识符teacher并不存在。

import语句包含as关键字时,完全限定名中的最后一个模块将绑定至当前命名空间,并采用as指定的标识符。

在书写语句import school.teacher as t之后,你可以通过t来使用模块teacher,因为标识符t将于模块teacher绑定。

_开头的模块特性并没有访问上的限制,通过模块teacher使用变量__avg_age和函数_add_avg_age不会有任何问题。

import.py
# 使用 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后,可在函数外部通过父级包schoolteacher特性访问teacher模块,而不需要明确的导入。

import_attr.py
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_infofloat_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模块的所有特性,并在当前命名空间中绑定为countlevelshow。语句count=100并不会对teacher模块的count变量进行赋值,而是会定义新的变量count

此外,由于*并不导入 Python 模块中以_开头的特性,因此,访问teacher模块的__avg_age特性将导致异常NameError

import_from.py
# 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模块的特性。

import_parent.py
# 模块 student 将通过父级包导入其他模块
import school.student
school/student.py
# 以相对路径导入 teacher 模块的特性
from .teacher import *

print('在 student 模块中调用 show') show()
# 以相对路径导入 guard 模块 from . import guard print(f'守卫人员是 {guard.name}')
在 student 模块中调用 show
教师的数量为 0
守卫人员是 汪汪大队
school/guard.py
# 模块变量 name
name = '汪汪大队'

使用 from…import * 语句不能导入 Python 包中的所有模块

使用from…import *不能导入 Python 包中的所有模块,*仅针对包的特性。如果在执行from…import *语句之前,Python 包的一些子模块已经被导入,那么已导入的子模块将被from…import *绑定至当前命名空间(因为他们是 Python 包的特性),此时可以直接书写子模块名称来使用子模块。

由于之前已经导入了teacher模块,因此该模块是包school的特性之一,from…import语句会将其绑定至当前命名空间,书写teacher没有任何问题。

另一个子模块student并非包school的特性,因此from…import语句不会将student绑定至当前命名空间。

import_from.py
# 由于 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