URLhttps://learnscript.net/zh/python/modules/
    复制链接转到说明  示例

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

    我被代码海扁署名-非商业-禁演绎
    阅读 16:48·字数 5040·更新 

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

    Python 模块

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

    如何命名模块?

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

    模块的存储形式

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

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

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

    Python 模块的特性

    在 Python 脚本文件中,你可以定义变量,函数,类等内容,这些内容即为模块的特性(Attribute),他们通过形式为m.namem.name=value的语句被访问,其中m为模块,name为特性(名称,标识符),value为特性的新值。

    当然,模块特性可以被称呼为变量,函数或类,这并没有什么问题。

    如何查看模块的所有特性?

    模块拥有的特殊特性__dict__,包含了模块所有特性的信息。__dict__本身是一个字典,字典的键是表示特性名称的字符串,字典的值是特性的具体内容。

    对模块特性的访问,等同于对__dict__键值对的访问,假设模块student拥有特性count,那么,student.count=100等同于student.__dict__['count']=100

    如何为模块添加暂时特性?

    在没有限制的情况下,你可以为一个模块添加暂时特性,只需要使用正常的赋值语句m.name=value即可。其中m为模块,name为特性(名称,标识符),value为特性的值。

    如何判断模块是否为程序入口点?

    模块的__name__特性表示模块的名称,该特性可在代码中修改。当一个模块被当作程序入口点时,其模块名称将被设置为'__main__',因此,可通过判断__name__是否等于'__main__',来执行模块作为程序入口点时的特定代码。

    如何获取模块对应的文件路径?

    模块的__file__特性是模块对应的文件路径,如果该模块是通过文件加载的。当模块通过非文件方式加载时,那么他可能不具备__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 模块的私有特性

    模块有没有私有特性,是模棱两可的,你可以为特性指定以一个或更多下划线(_)开头的名称,以表示该特性不适合被外部访问。但事实上,并不会阻止此类情况的发生,通过模块访问以下划线开头的特性是被允许的。

    模块中以下划线(_)开头的特性,可能不会被from…import语句导入,这一点会在稍后给予说明。

    Python 模块的文档字符串

    与函数类似,模块也具有文档字符串,他被存储在模块的__doc__特性中,以向开发人员说明模块的相关信息。文档字符串本质上是书写在模块中的字符串字面量,他必须是模块的第一行有效代码,可以采用紧密相邻或加入空白的方式进行拼接,但不应包含需要运算的内容或操作。

    在模块teacher中,我们拼接了两个字符串字面量作为模块的文档字符串。

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

    创建 Python 模块

    当你创建一个py脚本时,也就创建了一个模块,这是最为简单和常见的方式。除此之外,你还可以通过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 模块

    要在一个模块中使用另一个模块或模块的某个特性,必须先进行导入操作,通常要用到importfrom…import语句。这两种语句的用法不同,但均会将目标绑定至当前的命名空间,其对应的标识符默认为被绑定目标的名称。

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

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

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

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

    命名空间,作用域

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

    使用 import 语句导入 Python 模块

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

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

    module 部分

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

    identifier 部分

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

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

    虽然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 语句导入的模块或特性?

    from…import语句中,关键字import所指示的导入目标,将被绑定至当前的命名空间,因此,他并不会像import语句一样让你产生疑惑。比如,执行from school.teacher import show后,你可以直接书写show()来调用teacher模块的show函数。

    如何使用 from…import 语句导入模块的所有特性?

    from…import语句的import关键字后跟随*,即可导入相关模块的所有特性,这些特性将通过自己的名称绑定至当前命名空间。此做法仅被允许出现在模块级别,函数或方法使用*将导致错误。

    上述中所谓的“所有”并不包含以一个或更多_开头的(私有)特性,如果需要,你可以在from…import语句中单独的明确的导入他们。比如,from school.teacher import __avg_age明确的导入了teacher模块的__avg_age特性。

    不能赋值修改 from…import 语句导入的模块变量

    一旦使用from…import语句导入了模块中的变量,就可以读取该变量,但无法直接修改他,如果你试图对其进行赋值,将等同于在当前位置定义一个新的变量。

    在下面的代码中,我们导入了teacher模块的所有特性,并在当前命名空间中绑定为countlevelshow。语句count=100并不会对teacher模块的count变量进行赋值,而是会定义新的变量count

    此外,由于*并不导入模块中以_开头的特性,因此,访问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 语句导入当前模块的父级包?

    from…import语句的target部分中采用相对路径(以.开头),即可导入当前模块的父级包,如果书写..,则将导入父级包以及父级包的父级包。在有些情况下,是否书写.均可导入同一目录中的其他模块,但由于书写.会额外的尝试导入父级包,因此这可能导致异常ImportError的发生,当模块没有相关父级包时。

    需要指出,在包对应的__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 * 语句不能导入包中的所有模块

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

    由于之前已经导入了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

    源码

    src/zh/modules·codebeatme/python·GitHub