User: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
的、父包文件夹的子文件夹。
这里我们执行以下步骤以创建一个简单的包。
- 把上面“简单模块”章节中的
MyModule.py
重命名为__init__.py
- 新建一个
MyModule
文件夹,把__init__.py
移动进去,形成这样的结构:
folder/
test.py
MyModule/
__init__.py
我们运行 test.py
,输出和上面的“简单模块”章节中的内容是一致的。
import 语句与包[编辑 | 编辑源代码]
我们依旧使用上面的示例。如果我们需要导入tkinter
中 ttk
子模块时,应当这样导入:
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
此时,如果 tkinter.ttk
中,包含 from . import test
的语句,它会把 .
翻译为 tkinter.ttk
。而 from .. import test
的语句,则会把 ..
翻译为 tkinter
。以此类推。
如果使用相对导入,则该文件不应该被直接执行。在 Python 中,被直接执行(而非导入)的文件总会以 '__main__'
为名称。比如:
print(__name__) #输出:__main__
此时,相对导入的部分就会被破坏掉,并导致报错。
__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("第二个数字:"))
))
包中的 __main__.py[编辑 | 编辑源代码]
这对于模块本身是一般的 Python 代码文件时很好理解,但如果模块是一个包呢?
我们有可以直接执行包内容的方法:在命令行中输入python -m PackageName
。此时,与 import
导入 __init__.py
不同,它会运行包中的 __main__.py
。
__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
,则 MyModule
和 MyModule.SubModule
都会被写入缓存中。
使用查找器[编辑 | 编辑源代码]
如果在缓存中找不到,则会使用查找器来进一步查找。读者无需详细了解查找器的内容,简单来讲,查找器会查找:
- 内置模块
比如keyword
、math
等。 - 文件路径
当前运行的文件,其所在文件夹中是否有对应模块。 - 安装的模块路径
已经安装的模块所在的路径。
已经安装的模块所在路径[编辑 | 编辑源代码]
以Windows系统为例,默认是安装在 %AppData%\Python\Python312\site-packages
dir 函数[编辑 | 编辑源代码]
内置函数 dir
会输出模块、类、函数等对象中,已有的属性列表。比如:
dir(float)
会输出:
['__abs__', '__add__', '__bool__', '__ceil__', '__class__', '__delattr__', '__dir__', '__divmod__', '__doc__', '__eq__', '__float__', '__floor__', '__floordiv__', '__format__', '__ge__', '__getattribute__', '__getformat__', '__getnewargs__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__int__', '__le__', '__lt__', '__mod__', '__mul__', '__ne__', '__neg__', '__new__', '__pos__', '__pow__', '__radd__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rmod__', '__rmul__', '__round__', '__rpow__', '__rsub__', '__rtruediv__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__trunc__', 'as_integer_ratio', 'conjugate', 'fromhex', 'hex', 'imag', 'is_integer', 'real']