用户:Xyy23330121/Python/模组


我们之前接触过 import 语句以导入模块。本章将详细讲解 import 语句的工作方式,如何编写模块,以及如何安装第三方模块。

简单模块 编辑

任何 .py 的文件都是一个简单的模块。为简单起见,我们新建一个文件夹,在其中放两个文件:

folder/
    MyModule.py
    test.py

其中 MyModule.py 的内容为:

#!/usr/bin/python
# -*- coding: utf-8 -*-

var = "变量"

def f(): print("函数")

class C:
	def __str__(self): return "类"

test.py 的内容为:

#!/usr/bin/python
# -*- coding: utf-8 -*-

import MyModule

print(MyModule.var)
MyModule.f()
obj = MyModule.C()
print(obj)

运行 test.py,输出为:

变量
函数
类

可见,我们可以正常导入之前已经写过的文件当中的变量、函数以及类。而 MyModule.py 就已经是一个模块了。

拓展:module 对象 编辑

模块同类、函数、一般的变量一样,它也是一个对象。对于该对象的具体操作暂不赘述,这里仅作为拓展。

简单包 编辑

如果一个文件夹具有 __init__.py 文件,则该文件夹会被视为一个模块(包)。

提示
对于文件系统中的体现,也不是没有例外,命名空间包就不需要子包一定在父包的目录下。但这是一种较高级的特性了,这里不多涉及这一种包。

就像维基学院的页面可以一个页面有几个分页面一样,模块的包也可以一个包包含几个子包。父包和子包在文件系统中的体现,就是父包是带有 __init__.py 的父文件夹,而子包是带有 __init__.py 的、父包文件夹的子文件夹。

这里我们执行以下步骤以创建一个简单的包。

  1. 把上面“简单模块”章节中的 MyModule.py 重命名为 __init__.py
  2. 新建一个 MyModule 文件夹,把 __init__.py 移动进去,形成这样的结构:
folder/
    test.py
    MyModule/
        __init__.py

我们运行 test.py,输出和上面的“简单模块”章节中的内容是一致的。

import 语句与包 编辑

我们依旧使用上面的示例。如果我们需要导入tkinterttk 子模块时,应当这样导入:

import tkinter.ttk

但是,如果用上面的方法进行导入。我们在使用包的内容时,则需要用以下方式,写明包的全称来调用包中的东西,比如:

tkinter.ttk.Label(text = "test")

我们有两种解决方法。第一种是 import ... as ...,第二种是利用 from ... import ...

from ... import ... 编辑

类似 from math import trunc 可以让之后调用 math.trunc 时无需标注包名,直接用 trunc 即可。相对调用也可以用于省略父包的名称。比如:

from tkinter import ttk
ttk.Label(text = "test")  #相比上面的调用,省略了"tkinter."

这对于前缀过长时十分好用。

相对导入 编辑

对于包中的内容而言,支持一种称作“相对导入”的方式。我们从文件树开始。

folder/
    __init__.py
    test.py
    MyModule/
        __init__.py
        test2.py
        test3.py
        SubModule/
            __init__.py
            test4.py
    MyModule2/
        __init__.py
        test5.py

此时,在 test2.py 中支持以下操作:

from . import test3               #导入test3.py
from .. import test               #导入test.py
from .SubModule import test4     #导入test4.py
from ..MyModule2 import test5    #导入test5.py

注意:类的名称 编辑

相对导入是关乎模块名称的。我们在导入模块后,可以用 __name__ 属性来访问模块的名称,比如:

import tkinter.ttk as ttk
print(ttk.__name__)  #输出:tkinter.ttk

而直接被执行的(而非被导入)的文件总会以'__main__' 为名称。比如我们直接执行以下代码:

print(__name__)  #输出:__main__

相对导入和文件的 __name__ 属性有关。在导入时,会按照被导入文件的 __name__ 属性决定相对导入的对象。之所以上面在 test2.py 中的操作可以成功,是因为我们先执行了:

import folder.MyModule.test2

此时,test2.py__name__ 属性为 folder.MyModule.test2。在导入 test2.py 中代码时,test2.py 中的内容会按照 test2.py__name__ 属性,被解释为:

from . import test3               #"." 被替换为 "folder.MyModule."
from .. import test               #".." 被替换为 "folder."
from .SubModule import test4     #"." 被替换为 "folder.MyModule."
from ..MyModule2 import test5    #".." 被替换为 "folder."

若直接运行 test2.pytest2.py__name__ 属性变为了 __main__,此时就不适用于相对导入。使用了相对导入的文件不应该被直接执行。

特别的,对于 folder/__init__.py ,直接用:

from MyModule import test2

也是相对导入。

__main__ 编辑

由于上面的特性,我们可以通过 __name__ 来判断文件是否被直接执行。比如:

if __name__ == "__main__":
    print("被直接执行")
else:
    print("不被直接执行")

此时,可以通过不同的运行状况,来使用对应的策略。比如:

def times(a,b):
    return a*b

if __name__ == "__main__":
    print(
     times(
      float(input("输入两个数字,输出乘法结果。\n第一个数字:")),
      float(input("第二个数字:"))
     ))

直接执行包 编辑

对于仅有单个 .py 文件组成的模块,直接执行模块就是直接执行该 .py 文件。而对于包而言,直接执行模块时,会执行其目录中的 __main__.py 文件。

读者既可以打开包的位置并手动执行其中的 __main__.py,也可以在比如 cmd.exe 中使用以下指令,直接执行已被安装的包:

python -m PackageName

如果一个包中没有 __main__.py,则那个包就不应被直接执行。

导入 __main__ 编辑

我们也可以从模块中导入 __main__ 的内容。比如:

folder/
    importmain.py
    test.py

其中 importmain.py 的内容为:

#!/usr/bin/python
# -*- coding: utf-8 -*-

import __main__

def prtvar: print(__main__.var)

test.py 的内容为:

#!/usr/bin/python
# -*- coding: utf-8 -*-

import importmain

var = "__main__中的变量"
importmain.prtvar()

运行 test.py,输出为:

__main__中的变量

可以看出,我们在模块中反过来调用了当时直接运行的文件中的变量。每个直接运行的文件,在运行中,可以被视为以 __main__ 为名称的模块。

模块参数 编辑

在模块中,一些特殊的变量在运行时可能有特殊的效果。如果模块是包,这些变量应该写在 __init__.py 里。

__all__ 编辑

from Module import * 时,导入的内容名称列表。比如在 json 模块中,有:

__all__ = [
    'dump', 'dumps', 'load', 'loads',
    'JSONDecoder', 'JSONDecodeError', 'JSONEncoder',
]

如果我们使用:

from json import *

相当于:

from json import dump, dumps, load, loads, JSONDecoder, JSONDecodeError, JSONEncoder

如果不设置此项,则默认会导入所有对象(如果是包,还会导入所有子模块),这会导致运行时间延长,并可能导致一些其它的副作用。

其它自定义参数 编辑

还有一些其它参数可以使用。这些变量在导入时作用不大,但在一些情况下可能有帮助。

参数名 类型 解释
__version__ 字符串 表示模块版本的字符串
__author__ 字符串 表示模块作者,经常还包含作者的电子邮箱。比如
'Sample Name <Sample@example.com>'

导入时参数 编辑

上面 Python 文档中所示属性会在导入时自动写入,并可以被调用。

import 语句查找模块的顺序 编辑

如果每创建一个新项目,都要把之前写的模块复制到项目文件夹下,无疑是很麻烦的。我们之前导入 keyword 模块时,也没有在项目文件夹中创建 keyword.py。可见,导入模块不需要把模块文件放在项目文件夹下。

对于一个语句 import Module,Python 会按先后顺序查询以下内容。

模块缓存 编辑

在运行 Python 时,会先查询已有的模块缓存 sys.modules。该缓存保存了已经调用的所有模块的信息。如果新调用的模块恰好在这些信息内,则会直接从该信息调用新模块。

比如,如果之前导入过 MyModule.SubModule ,则 MyModuleMyModule.SubModule 都会被写入缓存中。

使用查找器 编辑

如果在缓存中找不到,则会使用查找器来进一步查找。读者无需详细了解查找器的内容,简单来讲,查找器会查找:

  1. 内置模块
    比如 keywordmath等。
  2. 文件路径
    当前运行的文件,其所在文件夹中是否有对应模块。
  3. 安装的模块路径
    已经安装的模块所在的路径。

已经安装的模块所在路径 编辑

以Windows系统为例,默认是安装在 %AppData%\Python\Python312\site-packages

安装第三方模块 编辑

除 Python 的内置模块、读者自己撰写的模块之外。Python 社区还提供了许多第三方模块。

我们一般使用 pip 来安装第三方模块。pip 是 Python 附带的包管理器,我们可以在 cmd.exe 或其它类似的命令行程序中,输入以下的内容:

pip install numpy

并按回车,以安装第三方的 numpy 模块。

搜寻第三方库 编辑

在安装之前,读者应当先了解到第三方库的具体内容。比如我们希望能找到更快地、进行数字计算的方式,具体有以下几类方法:

  • 搜索引擎:在搜索引擎上搜索形如“Python 高效数字计算”的内容。
    如果使用谷歌等外国搜索引擎,还可以搜索这些关键词的英文版本。
  • 第三方社区:在 Stack Overflow 或 zhihu 等问答网站上,搜索类似的关键词。
  • Python官方的指引:前往 https://www.python.org/ 找到 Use Python for… 章节,并查看其内容。

在搜寻时,要重点关注第三方库的文档。文档会对安装和使用方法有较为详细的介绍。有的文档甚至附带使用教程。

解决安装疑难 编辑

由于中国大陆的网络环境过于“良好”,读者可能出现下载超时等情况导致安装失败。为此,有时需要添加额外的安装参数。

通过中国大陆的来源安装 编辑

中国大陆内部有许多组织会在自己的服务器上“托管”一些常用的 Python 包。以清华大学为例,我们可以尝试使用以下的方式,从清华大学的服务器下载并安装 numpy:

pip install -i https://pypi.tuna.tsinghua.edu.cn/simple numpy

通过代理安装 编辑

如果读者可以通过代理服务器来访问互联网,则可以使用以下方法:

pip install --proxy 127.0.0.1:1080 numpy

这里的代理服务器设置为 127.0.0.1:1080。读者应当随着自己的代理服务器情况自行调整设置。

调整超时限制 编辑

默认情况下,下载服务器在 15 秒内没有响应就会超时。我们可以添加一些参数来修改超时时间:

pip install --timeout 99999 numpy

这里设置为 99999 秒。

组合使用 编辑

以上的参数可以结合起来使用。比如:

pip install -i https://pypi.tuna.tsinghua.edu.cn/simple --timeout 99999 numpy

其它疑难杂症 编辑

对于其它的疑难杂症,我们可以输入以下内容,查看使用帮助:

pip help

学习第三方模块的使用方法 编辑

第三方模块的文档许多都是英文,如果读者有足够的英文功底,或者能使用翻译软件,可以尝试直接阅读第三方模块的官方文档。如果使用翻译软件,最好是对照着中英文来阅读,因为翻译可能不准确。

如果没有足够的英文功底,读者就需要到网站上,找到类似本资料的中文学习资料。此时读者已经经过“搜寻第三方库”找到了第三方库的名称,可以直接上搜索引擎搜索比如“Python numpy”的内容,社区会为读者提供帮助的。

如果有照着教程或文档使用还不了解的内容,读者可以选择去 Stack Overflow 等问答网站上求助,也可以自己做一些测试。之前章节中 这个页面 就是本资料作者进行的测试。