- 开胃菜
- 使用 Python 解释器
- 非正式介绍Python
- 深入流程控制
- 数据结构
- 模块
- 输入和输出
- 错误和异常
- 类
- 标准库的简明介绍
- 标准库的简明介绍(第二部分)
- 现在干什么?
- 交互式输入编辑及历史替代
- 浮点算术: 问题和限制
如果你从 Python 解释器退出后再重新进入, 那么你之前定义的所有 (函式和变量) 都将丢失. 因此, 如果你想写一个更长的程序, 你最好离线地使用文本编辑器保存成文件,替代解释器的输入来运行. 这称作创建一个 脚本 . 当你的程序变得更长, 你可能想把它分割成几个文件以能够更简单地维护. 你也许还想在几个下同的程序里使用写过的程序, 而不用把一坨代码拷来拷去.
为此 Python 提供了方法, 能使用户把定义存放在文件里, 同时又能在脚本或交互式环境下方便的使用它们. 这样的文件称为 模块
; 一个 模块
中的定义可以 导入(import)
到另一个模块或 主 模块 ( 主 模块是执行脚本的最上层或计算模式下的一组可访问变量的集合).
模块就是包含 Python 定义和语句的文件. 文件的名字就是这个模块名再加上 .py
. 在一个模块中, 模块的名字 (一个字符串) 可以通过全局变量 __name__
得到. 例如, 使用你最喜欢的文档编辑器在当前目录下创建一个名为 fibo.py
的文件, 并输入以下内容:
# Fibonacci 数列模块
def fib(n): # 打印小于 n 的 Fibonacci 数列
a, b = 0, 1
while b < n:
print(b, end=’ ‘)
a, b = b, a+b
print()
def fib2(n): # 返回小于 n 的 Fibonacci 数列
result = []
a, b = 0, 1
while b < n:
result.append(b)
a, b = b, a+b
return result
现在打开 Python 解释器并通过以下命令 导入(import)
这个模块:
>>> importfibo
这样并不会把 fibo
中定义的函式名 导入(import)
到当前的符号表里; 它只导入了模块名 fibo
. 你可以使用模块名来访问函式:
>>> fibo.fib(1000)
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987
>>> fibo.fib2(100)
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
>>> fibo.__name__
‘fibo’
如果你要经常使用一个函式的话, 可以把它赋给一个局部变量:
>>> fib = fibo.fib
>>> fib(500)
1 1 2 3 5 8 13 21 34 55 89 144 233 377
6.1. 深入模块
模块不仅包含函式定义, 还可以包含可执行的语句. 这些语句一般用以初始化模块. 他们仅在模块 第一 次被导入时才被执行. [1]
每个模块有其私有的符号表, 由模块内部定义的函式当成全局符号表来使用. 因此, 模块的作者可以在模块中放胆使用全局变量而无需担心与用户的全局变量发生冲突. 另一方面, 当你确实明白你在做什么的时候, 你可以 通过 modname.itemname 形式来访问模块的全局变量.
模块中可以导入其它的模块. 习惯上把 import
语句放在一个模块 (或者脚本, ) 的最开始, 当然这只是惯例不是强制的. 被导入模块的名称被放入当前模块的全局符号表里.
import
语句这有一种不同用法, 它可以直接把一个模块内(函式,变量)名称导入当前模块符号表里. 例如:
>>> fromfiboimport fib, fib2
>>> fib(500)
1 1 2 3 5 8 13 21 34 55 89 144 233 377
这样不会导入相应的模块名 (在这个例子里, fibo
并没有被定义).
还有一种方法可一次性导入模块中所有的名字定义:
>>> fromfiboimport *
>>> fib(500)
1 1 2 3 5 8 13 21 34 55 89 144 233 377
这样可以导入除以下划线开头 (_
) 的所有名字. 多数情况中, Python 程序员不使用这个窍门, 因为它导入了一些未知的名字到解释器里, 因此可能会意外重载一些你已经定义的东西.
注意: 在一般的实践中, 导入 *
是不好的, 因为它常常产生难以阅读的代码. 然而, 在一个交互式会话里使用它可以节省键入.
Note :因为效率的原因, 每个模块在每个解释器会话中只被导入一次. 一旦你修订了你的模块, 就需要重启解释器 —- 或者, 若你只是想交互式地测试一个模块, 使用 imp.reload()
, 例如 import imp; imp.reload(modulename)
.
6.1.1. 把模块当脚本执行
当你以如下方式运行一个 Python 模块时
python fibo.py <arguments>
模块中的代码就会被执行, 就像被导入时一样, 但 __name__
被设为 "__main__"
. 这就意味着通过在模块最后加入以下代码:
if __name__ == “__main__”:
importsys
fib(int(sys.argv[1]))
就能够把这个文件既当成脚本使用, 也可以当成可导入的模块使用, 因为解析命令行的代码只当模块被当成 “主(main)” 时才被直接运行:
$ python fibo.py 50
1 1 2 3 5 8 13 21 34
如果模块被导入, 这段代码并不执行:
>>> importfibo
>>>
模块常通过这种形式提供一些方便的用户接口, 或用于测试 (把模块当脚本执行一个测试套件).
6.1.2. 模块搜索路径
当名为 spam
的模块导入时, 解释器会先从内建模块尝试匹配. 如果没找到, 则将在 sys.path
记录的所有目录中搜索 spam.py
文件. 而 sys.path
则由以下场景声明:
- 包含导入脚本的目录 (即当前目录)
PYTHONPATH
(一个目录列表,其形式同shell 变量PATH
的语法).- 安裝时定义的默认目录.
Python程序初始化后, 依然可以修改 sys.path
. 该目录包含运行的脚本放置在搜索路径的开始, 标准库的路径之前. 这意味着, 在当前目录中的脚本将被优先加载, 所以如果同系统模块有重名现象时, 这通常会抛出一个错误.
参看 标准模块 小节获取更多信息.
6.1.3. “已编译” 的 Python 文件
为了减少使用了大量标准模块的小程序的启动时间, 如果 spam.py
所在目录下中有名为 spam.pyc
的文件, 解释器就会优先导入 spam
模块的这一 “已编译字节” 版本文件. 用来创建 spam.pyc
的 spam.py
的版本修改时间被记录在 spam.pyc
中, 如果不匹配的话, .pyc
文件就会被忽略.
一般, 你无需特意做什么来创建 spam.pyc
文件. 每次 spam.py
被成功编译后, 都会尝试把结果写入到 spam.pyc
. 这时有任何问题,并不会抛出错误; 如果因某些原因导致这文件没有被完全的被写入, 那么产生的 spam.pyc
文件会被辨别出是无效的, 从而在导入时被忽略. spam.pyc
文件的内容是平台无关的, 因此, 一个 Python 模块目录可以在不同的体系架构中共享.
给专家的小技巧:
- 当使用
-O
参数来调用Python 解释器时, python会对代码进行优化,并存入在.pyo
文件里. 当前优化器仅仅只是移除了assert
语句. 当使用-O
时, 所有 bytecode 都被优化了; 所有 .pyc 文件被忽略, 而.py
文件被编译为优化的字节码. - 传递两个
-O
参数到 Python 解释器 (-OO
) 会使编译器对字节码进一步优化, 而该步骤在极少的情况下会产生发生故障的程序. 一般地, 只是将__doc__
字符串被从字节码中移除, 以产生更为紧凑的.pyo
文件. 因为有些程序可能依赖于这些, 因此, 建议只有当你真正明确这意味着什么时,才使用这个选项. - 程序从
.pyc
或.pyo
文件里读取时, 并不会比它从.py
文件中读取会有更快的执行速度; 唯一提高的是载入速度. - 在在命令行中直接调用脚本运行时, 编译后的字节码不会被写入
.pyc
或.pyo
文件. 因此, 通过移动该脚本的大量代码到一个模块, 并由一个小的引导脚本来导入这个模块, 可能减少这个脚本的启动时间. 也可以直接在命令行里直接命名一个.pyc
或.pyo
文件. - 对于同一个模块, 可以只包含
spam.pyc
(或者spam.pyo
当使用-O
时) 文件而无需spam.py
文件. 使用这种形式可用以发布 Python代码库, 并使得反编译工程有一定的难度. - 模块
compileall
可以为一个目录下的所有模块创建.pyc
文件 (或.pyo
文件, 当使用-O
时).
6.2. 标准模块
Python 本身带有一个标准库, 有专门文档: Python 库参考 (以后简称 “库参考”)进行介绍.
有些模块内建到了解释器中; 有些操作尽管并不是语言核心的一部分, 但是通过模块内建提供后,执行效率不错, 包含操作系统的一些基本访问, 例如系统调用.
这种模块能根据不同的操作系统进行专门配置, 例如, winreg
模块只在 Windows 系统中提供. 有一个特别的模块需要特别注意: sys
, 它内建于每个 Python 解释器. 其中变量 sys.ps1
和 sys.ps2
定义了用于主和次提示符的字符串:
>>> importsys
>>> sys.ps1
‘>>> ‘
>>> sys.ps2
‘… ‘
>>> sys.ps1 = ‘C> ‘
C> print(‘Yuck!’)
Yuck!
C>
只有解释器在交互模式下运行时,这两个变量才有定义.
变量 sys.path
是一个字符串列表, 它为解释器指定了模块的搜索路径. 它通过环境变量 PATHONPATH
初始化为一个默认路径, 当没有设置 PYTHONPATH
时, 就使用内建默认值来初始化. 你可以通过标准列表操作来修订之:
>>> importsys
>>> sys.path.append(‘/ufs/guido/lib/python’)
6.3. dir()
函式
内建函式 dir()
用于找出一个模块里定义了那些名字. 它返回一个有序字串列表:
>>> importfibo, sys
>>> dir(fibo)
[‘__name__’, ‘fib’, ‘fib2’]
>>> dir(sys)
[‘__displayhook__’, ‘__doc__’, ‘__excepthook__’, ‘__name__’, ‘__stderr__’,
‘__stdin__’, ‘__stdout__’, ‘_getframe’, ‘api_version’, ‘argv’,
‘builtin_module_names’, ‘byteorder’, ‘callstats’, ‘copyright’,
‘displayhook’, ‘exc_info’, ‘excepthook’,
‘exec_prefix’, ‘executable’, ‘exit’, ‘getdefaultencoding’, ‘getdlopenflags’,
‘getrecursionlimit’, ‘getrefcount’, ‘hexversion’, ‘maxint’, ‘maxunicode’,
‘meta_path’, ‘modules’, ‘path’, ‘path_hooks’, ‘path_importer_cache’,
‘platform’, ‘prefix’, ‘ps1’, ‘ps2’, ‘setcheckinterval’, ‘setdlopenflags’,
‘setprofile’, ‘setrecursionlimit’, ‘settrace’, ‘stderr’, ‘stdin’, ‘stdout’,
‘version’, ‘version_info’, ‘warnoptions’]
不给参数时, dir()
就罗列出当前已定义的所有名字.
>>> a = [1, 2, 3, 4, 5]
>>> importfibo
>>> fib = fibo.fib
>>> dir()
[‘__builtins__’, ‘__doc__’, ‘__file__’, ‘__name__’, ‘a’, ‘fib’, ‘fibo’, ‘sys’]
注意, 它列举出了所有类型的名字: 变量, 模块, 函式, 等等.
dir()
并不列出内建函式和变量的名字. 如果你真心想看一下, 可以直接查询标准模块 buildin
>>> importbuiltins
>>> dir(builtins)
[‘ArithmeticError’, ‘AssertionError’, ‘AttributeError’, ‘BaseException’, ‘Buffer
Error’, ‘BytesWarning’, ‘DeprecationWarning’, ‘EOFError’, ‘Ellipsis’, ‘Environme
ntError’, ‘Exception’, ‘False’, ‘FloatingPointError’, ‘FutureWarning’, ‘Generato
rExit’, ‘IOError’, ‘ImportError’, ‘ImportWarning’, ‘IndentationError’, ‘IndexErr
or’, ‘KeyError’, ‘KeyboardInterrupt’, ‘LookupError’, ‘MemoryError’, ‘NameError’,
‘None’, ‘NotImplemented’, ‘NotImplementedError’, ‘OSError’, ‘OverflowError’,
‘P endingDeprecationWarning’, ‘ReferenceError’, ‘RuntimeError’,
‘RuntimeWarning’, ‘ StopIteration’, ‘SyntaxError’, ‘SyntaxWarning’,
‘SystemError’, ‘SystemExit’, ‘Ta bError’, ‘True’, ‘TypeError’,
‘UnboundLocalError’, ‘UnicodeDecodeError’,
‘Unicod eEncodeError’, ‘UnicodeError’, ‘UnicodeTranslateError’,
‘UnicodeWarning’, ‘UserW arning’, ‘ValueError’, ‘Warning’,
‘ZeroDivisionError’, ‘__build_class__’, ‘__deb ug__’, ‘__doc__’, ‘__import__’,
‘__name__’, ‘__package__’, ‘abs’, ‘all’,
‘any’, ‘ascii’, ‘bin’, ‘bool’, ‘bytearray’, ‘bytes’, ‘chr’, ‘classmethod’, ‘compile’,
‘ complex’, ‘copyright’, ‘credits’, ‘delattr’, ‘dict’, ‘dir’, ‘divmod’, ‘enumerate ‘,
‘eval’, ‘exec’, ‘exit’, ‘filter’, ‘float’, ‘format’, ‘frozenset’, ‘getattr’, ‘globals’, ‘hasattr’,
‘hash’, ‘help’, ‘hex’, ‘id’, ‘input’, ‘int’, ‘isinstance’,
‘issubclass’, ‘iter’, ‘len’, ‘license’, ‘list’, ‘locals’, ‘map’, ‘max’, ‘memory
view’, ‘min’, ‘next’, ‘object’, ‘oct’, ‘open’, ‘ord’, ‘pow’, ‘print’, ‘property’ ,
‘quit’, ‘range’, ‘repr’, ‘reversed’, ’round’, ‘set’, ‘setattr’, ‘slice’, ‘sort
ed’, ‘staticmethod’, ‘str’, ‘sum’, ‘super’, ‘tuple’, ‘type’, ‘vars’, ‘zip’]
6.4. 包
包是一种 Python 模块命名空间的组织方法, 通过使用 “带点号的模块名”. 例如, 模块名 A.B
指定了一个名为 A
的包里的一个名为 B
的子模块. 就像模块的使用使不同模块的作者避免担心其它全局变量的名字, 而带点号的模块使得多模块包, 例如 NumPy 或 Python 图像库, 的作者避免担心其它模块名.
假设你想设计一个模块集 (一个 “包”), 用于统一声音文件和声音数据的处理. 有许多不同的声音格式 (通常通过它们的后缀来辨认, 例如: .wave
, .aiff
, .au
), 因此你可能需要创建和维护一个不断增长的模块集, 用以各种各样的文件格式间的转换. 还有许多你想对声音数据执行的不同操作 (例如混频, 增加回音, 应用一个均衡器功能, 创建人造的立体声效果), 因此, 你将额外的写一个永无止尽的模块流来执行这些操作. 这是你的包的一个可能的结构:
sound/ 顶级包
__init__.py 初始化这个声音包
formats/ 文件格式转换子包
__init__.py
wavread.py
wavwrite.py
aiffread.py
aiffwrite.py
auread.py
auwrite.py
…
effects/ 音效子包
__init__.py
echo.py
surround.py
reverse.py
…
filters
/ 过滤器子包
__init__.py
equalizer.py
vocoder.py
karaoke.py
…
当导入这个包时, Python 搜索 sys.path
上的目录以寻找这个包的子目录.
需要 __init__.py
文件来使得 Python 知道这个目录包含了包; 这用来预防名字为一个通用名字, 如 string
, 的目录以外地隐藏了在模块搜索路径靠后的正当的模块. 在最简单的例子里, __init__.py
可以就是个空文件, 但它也可以为这个包执行初始化代码, 或者设置 __all__
变量, 在后面描述.
包的用户可以包里的单独的模块, 例如:
importsound.effects.echo
这载入里 sound.effects.echo
子模块. 一定要使用全名来引用它.
sound.effects.echo.echofilter(input, output, delay=0.7, atten=4)
导入子模块的一个替代方法是:
fromsound.effectsimport echo
这样也载入 echo
子模块, 并且可以不加包前缀地使用, 因此可以如下地使用:
echo.echofilter(input, output, delay=0.7, atten=4)
另一个变种是直接导入想要的函式或变量:
fromsound.effects.echoimport echofilter
再一次, 载入了 echo
子模块, 但是使它的函式 echofilter()
可以直接使用.
注意, 当使用 from package import item
时, 这个项即可以是这个包的一个子模块 (或子包), 也可以是其它的定义在这个包里的名字, 如函式, 类或变量. import
语句首先测试这个项是否在包里定义; 如果没有, 就假设它是一个模块并试图载入它. 如果寻找它失败, 就会抛出一个 ImportError
.
相反地, 当使用 import item.subitem.subsubitem
时, 除最后的每一项都必须是包; 最后一项可以是模块或包, 但不能是在之前项中定义的类, 函式或变量.
6.4.1. 从包中导入 *
当开发者写下 from sound.effects import *
会发生什么?
理想地, 我们期望程序会以以某种方法进入文件系统, 寻找在指定的包文件中,找到所有子模块, 并把它们全部导入. 这可能花费很长的时间, 而且对子模块进行显式导入时,还可能引发非期待的 副作用!
~ 这些副作用可能是仅仅发生在显式导入子模块的时候才会发生的那些
译注
@Liam Huang
尽管此处并没有显式导入子模块 但是却会发生这类错误
对于包作者, 唯一解决方案是提供包的显式索引. import
语句有以下约定:
- 如果一个包的
__init__.py
代码定义了一个名为__all__
的列表, - 当遇到
from package import *
时, 它被用来作为导入的模块名字的列表.
是否在发布包的新版本时保持这个列表的更新取决于包的作者. 包作者也可能决定不支持它, 如果他们没有发现从他们的包里导入 * 的用途. 例如, 文件 sound/effects/__init__.py
可能包含如下代码
__all__ = [“echo”, “surround”, “reverse”]
这意味这 from sound.effects import *
将导入 sound
中这几个名字的子模块.
如果 __all__
没有被定义, from sound.effects import *
语句 不 把包 sound.effects
中所有的子模块都导入到当前命名空间里; 它只能确保包 sound.effects
被导入了 (可能同时运行在 __init__.py`
里的一些初始化代码), 并随后导入包中定义的任何名字. 这包含任何在 __init__.py
定义的 任何名字 (和显式载入的子模块). 它还包含通过前面的 import
语句显式载入的包的子模块. 考虑这段代码:
importsound.effects.echo
importsound.effects.surround
fromsound.effectsimport *
在这个例子中, 模块 echo
和 surround
被导入到当前命名空间, 因为当 执行 from...import
语句时它们就被定义在包 sound.effects
里. (当定义 __all__
定义时, 这也会工作.)
虽然有些模块被设计成当使用 import * 时仅导出遵循特定模式的名称, 但是在产品代码中仍然感觉算糟糕实践.
记住, 使用 from Package import specific_submodule 是没有问题的! 事实上, 这是推荐的用法, 只在以下情况例外: 正在导入的模块需要使用的子模块与其他包中的子模块具有相同的名字.
6.4.2. 内部包参考
当包被构造到子包时 (如例子中的 sound
包), 你可以独立地导入来获取兄弟包的子模块的引用. 例如, 如果模块 sound.filters.vocoder
需要使用 sound.effects
包下的 echo
模块, 就可以使用 from sound.effects import echo
.
你还可以使用相对导入, 通过 import 语句的 from module import name
格式. 这些导入使用句点来表明涉及这次相对导入的当前包和父包. 从例子中的 surround
, 您可以使用:
from.import echo
from..import formats
from..filtersimport equalizer
注意, 相对导入基于当前模块的名字. 因为主模块的名字总是 "__main__"
, 有意用作一个 Python 程序的主模块的模块必须总使用 绝对导入
.
6.4.3. 多目录的包
包支持额外一个特殊的属性, __path__
. 它在文件中的代码执行之前, 被初始化为一个列表, 它包含保存在这个包的 __init__.py
文件中目录名. 这个变量可以被更改; 这样做会影响以后对包中模块和子包的搜索.
虽然这个特性不经常需要, 但它可以用于扩展在一个包里发现的模块的集合.
Footnotes
[1] | 实际上, 函式定义也是 ‘被执行’ 的 ‘语句’; 模块级函式的执行让函式名进入这个模块的全局变量表. |