- 01Python历史变化
- 02Python语言特性
- 03Python术语对照
>>>...- 2to3
- abstract base class — 抽象基类
- annotation — 标注
- argument — 参数
- asynchronous context manager — 异步上下文管理器
- asynchronous iterator — 异步迭代器
- awaitable — 可等待对象
- binary file — 二进制文件
- bytes-like object — 字节类对象
- bytecode — 字节码
- callback — 回调
- class — 类
- class variable — 类变量
- coercion — 强制类型转换
- complex number — 复数
- context manager — 上下文管理器
- context variable — 上下文变量
- contiguous — 连续
- coroutine — 协程
- coroutine function — 协程函数
- CPython
- decorator — 装饰器
- descriptor — 描述器
- dictionary — 字典
- dictionary view — 字典视图
- docstring — 文档字符串
- duck-typing — 鸭子类型
- EAFP
- expression — 表达式
- extension module — 扩展模块
- file object — 文件对象
- file-like object — 文件类对象
- finder — 查找器
- floor division — 向下取整除法
- function — 函数
- function annotation — 函数标注
- future
- garbage collection — 垃圾回收
- generator — 生成器
- generator expression — 生成器表达式
- generic function — 泛型函数
- global interpreter lock — 全局解释器锁
- hash-based pyc — 基于哈希的 pyc
- hashable — 可哈希
- IDLE
- immutable — 不可变
- import path — 导入路径
- importing — 导入
- importer — 导入器
- interactive — 交互
- interpreted — 解释型
- interpreter shutdown — 解释器关闭
- iterable — 可迭代对象
- iterator — 迭代器
- key function — 键函数
- lambda
- LBYL
- list — 列表
- list comprehension — 列表推导式
- loader — 加载器
- magic method — 魔术方法
- mapping — 映射
- metaclass — 元类
- method — 方法
- module — 模块
- module spec — 模块规格
- mutable — 可变
- named tuple — 具名元组
- namespace — 命名空间
- namespace package — 命名空间包
- nested scope — 嵌套作用域
- new-style class — 新式类
- object — 对象
- package — 包
- parameter — 形参
- path entry — 路径入口
- path entry finder — 路径入口查找器
- path based finder — 基于路径的查找器
- path-like object — 路径类对象
- portion — 部分
- provisional API — 暂定 API
- Python 3000
- qualified name — 限定名称
- reference count — 引用计数
- regular package — 常规包
- slots
- sequence — 序列
- single dispatch — 单分派
- slice — 切片
- special method — 特殊方法
- statement — 语句
- text encoding — 文本编码
- text file — 文本文件
- triple-quoted string — 三引号字符串
- type — 类型
- type alias — 类型别名
- type hint — 类型提示
- universal newlines — 通用换行
- variable annotation — 变量标注
- virtual environment — 虚拟环境
- virtual machine — 虚拟机
- Zen of Python — Python 之禅
- 04Python常见问题
- 一般问题">一般问题
- 历史常见问题
- 为什么Python使用缩进来分组语句?">为什么Python使用缩进来分组语句?
- 为什么简单的算术运算得到奇怪的结果?">为什么简单的算术运算得到奇怪的结果?
- 为什么浮点计算不准确?">为什么浮点计算不准确?
- 为什么Python字符串是不可变的?">为什么Python字符串是不可变的?
- 为什么必须在方法定义和调用中显式使用“self”?">为什么必须在方法定义和调用中显式使用“self”?
- 为什么不能在表达式中赋值?">为什么不能在表达式中赋值?
- 为什么Python对某些功能(例如list.index())使用方法来实现,而其他功能(例如len(List))使用函数实现?">为什么Python对某些功能(例如list.index())使用方法来实现,而其他功能(例如len(List))使用函数实现?
- 为什么 join()是一个字符串方法而不是列表或元组方法?">为什么 join()是一个字符串方法而不是列表或元组方法?
- 异常有多快?">异常有多快?
- 为什么Python中没有switch或case语句?">为什么Python中没有switch或case语句?
- 难道不能在解释器中模拟线程,而非得依赖特定于操作系统的线程实现吗?">难道不能在解释器中模拟线程,而非得依赖特定于操作系统的线程实现吗?
- 为什么lambda表达式不能包含语句?">为什么lambda表达式不能包含语句?
- 可以将Python编译为机器代码,C或其他语言吗?">可以将Python编译为机器代码,C或其他语言吗?
- Python如何管理内存?">Python如何管理内存?
- 为什么CPython不使用更传统的垃圾回收方案?">为什么CPython不使用更传统的垃圾回收方案?
- CPython退出时为什么不释放所有内存?">CPython退出时为什么不释放所有内存?
- 为什么有单独的元组和列表数据类型?">为什么有单独的元组和列表数据类型?
- 列表是如何在CPython中实现的?">列表是如何在CPython中实现的?
- 字典是如何在CPython中实现的?">字典是如何在CPython中实现的?
- 为什么字典key必须是不可变的?">为什么字典key必须是不可变的?
- 为什么 list.sort() 没有返回排序列表?">为什么 list.sort() 没有返回排序列表?
- 如何在Python中指定和实施接口规范?">如何在Python中指定和实施接口规范?
- 为什么没有goto?">为什么没有goto?
- 为什么原始字符串(r-strings)不能以反斜杠结尾?">为什么原始字符串(r-strings)不能以反斜杠结尾?
- 为什么Python没有属性赋值的“with”语句?">为什么Python没有属性赋值的“with”语句?
- 为什么 if/while/def/class语句需要冒号?">为什么 if/while/def/class语句需要冒号?
- 为什么Python在列表和元组的末尾允许使用逗号?¶">为什么Python在列表和元组的末尾允许使用逗号?¶
- 核心语言">核心语言
- 当变量有值时,为什么会出现UnboundLocalError?">当变量有值时,为什么会出现UnboundLocalError?
- Python中的局部变量和全局变量有哪些规则?">Python中的局部变量和全局变量有哪些规则?
- 为什么在具有不同值的循环中定义的lambdas都返回相同的结果?">为什么在具有不同值的循环中定义的lambdas都返回相同的结果?
- 如何跨模块共享全局变量?">如何跨模块共享全局变量?
- 导入模块的“最佳实践”是什么?">导入模块的“最佳实践”是什么?
- 为什么对象之间会共享默认值?">为什么对象之间会共享默认值?
- 如何将可选参数或关键字参数从一个函数传递到另一个函数?">如何将可选参数或关键字参数从一个函数传递到另一个函数?
- 形参和实参之间有什么区别?">形参和实参之间有什么区别?
- 为什么更改列表 ‘y’ 也会更改列表 ‘x’?">为什么更改列表 ‘y’ 也会更改列表 ‘x’?
- 如何编写带输出参数的函数(通过引用调用)?">如何编写带输出参数的函数(通过引用调用)?
- 如何在Python中创建高阶函数?">如何在Python中创建高阶函数?
- 如何在Python中复制对象?">如何在Python中复制对象?
- 如何找到对象的方法或属性?">如何找到对象的方法或属性?
- 我的代码如何才能发现对象的名称?">我的代码如何才能发现对象的名称?
- 逗号运算符的优先级是什么?">逗号运算符的优先级是什么?
- 是否有与 C 的 “?:” 三目运算符等价的东西?">是否有与 C 的 “?:” 三目运算符等价的东西?
- 是否可以用Python编写混淆的单行程序?">是否可以用Python编写混淆的单行程序?
- 函数参数列表中的斜杠(/)是什么意思?">函数参数列表中的斜杠(/)是什么意思?
- 数字和字符串">数字和字符串
- 如何指定十六进制和八进制整数?">如何指定十六进制和八进制整数?
- 为什么-22 // 10返回-3?">为什么-22 // 10返回-3?
- 如何将字符串转换为数字?">如何将字符串转换为数字?
- 如何将数字转换为字符串?">如何将数字转换为字符串?
- 如何修改字符串?">如何修改字符串?
- 如何使用字符串调用函数/方法?">如何使用字符串调用函数/方法?
- 是否有与Perl 的chomp() 等效的方法,用于从字符串中删除尾随换行符?">是否有与Perl 的chomp() 等效的方法,用于从字符串中删除尾随换行符?
- 是否有 scanf() 或 sscanf() 的对应物?">是否有 scanf() 或 sscanf() 的对应物?
- ‘UnicodeDecodeError’ 或 ‘UnicodeEncodeError’ 错误是什么意思?">‘UnicodeDecodeError’ 或 ‘UnicodeEncodeError’ 错误是什么意思?
- 性能">性能
- 序列(元组/列表)">序列(元组/列表)
- 如何在元组和列表之间进行转换?">如何在元组和列表之间进行转换?
- 什么是负数序号?">什么是负数序号?
- 如何以相反的顺序迭代序列?">如何以相反的顺序迭代序列?
- 如何从列表中删除重复项?">如何从列表中删除重复项?
- 如何从列表中删除多个项?">如何从列表中删除多个项?
- 如何在Python中创建数组?">如何在Python中创建数组?
- 如何创建多维列表?">如何创建多维列表?
- 如何将方法应用于一系列对象?">如何将方法应用于一系列对象?
- 为什么 a_tuple[i] += [‘item’] 会在执行加法时引发异常?">为什么 a_tuple[i] += [‘item’] 会在执行加法时引发异常?
- 我想做一个复杂的排序:你能用Python做一个Schwartzian变换吗?">我想做一个复杂的排序:你能用Python做一个Schwartzian变换吗?
- 如何按其他列表中的值对一个列表进行排序?">如何按其他列表中的值对一个列表进行排序?
- 对象">对象
- 什么是类?">什么是类?
- 什么是方法?">什么是方法?
- 什么是 self ?">什么是 self ?
- 如何检查对象是否为给定类或其子类的一个实例?">如何检查对象是否为给定类或其子类的一个实例?
- 什么是委托?">什么是委托?
- 如何从覆盖基类的派生类调用基类中定义的方法?">如何从覆盖基类的派生类调用基类中定义的方法?
- 如何组织代码以便更改基类?">如何组织代码以便更改基类?
- 如何创建静态类数据和静态类方法?">如何创建静态类数据和静态类方法?
- 如何在Python中重载构造函数(或方法)?">如何在Python中重载构造函数(或方法)?
- 我尝试使用 spam ,但是得到一个关于 _SomeClassNamespam 的错误信息。">我尝试使用 spam ,但是得到一个关于 _SomeClassNamespam 的错误信息。
- 类定义了 del 方法,但是删除对象时没有调用它。">类定义了 del 方法,但是删除对象时没有调用它。
- 如何获取给定类的所有实例的列表?">如何获取给定类的所有实例的列表?
- 为什么
id()的结果看起来不是唯一的?">为什么id()的结果看起来不是唯一的?
- 模块">模块
参考鸣谢:
维基百科-Python
01Python历史变化
- 1989 吉多·范罗苏姆为了在打发时间,决心开发一个新的脚本解释编程,作为ABC语言的一种继承,替代使用Unix shell和C语言进行系统管理
- 1991年2月,范罗苏姆发布了最初代码(标记为版本0.9.0)于alt.sources[13],这时就已经存在了带继承的)类)、异常处理、函数)和核心数据类型
list、dict、str等。在这个最初发行中就有了从Modula-3引进的模块系统[14],它的异常模型也类似于Modula-3,但增加了else子句[7] - 1994年1月 发布Python版本1.0。这个发行版主要新特征是包括了函数式编程工具
[lambda](https://baike.tw.lvfukeji.com/baike-%E5%8C%BF%E5%90%8D%E5%87%BD%E6%95%B0#Python)、[map](https://baike.tw.lvfukeji.com/w/index.php?title=Map_(%E9%AB%98%E9%98%B6%E5%87%BD%E6%95%B0)&action=edit&redlink=1)、[filter](https://baike.tw.lvfukeji.com/w/index.php?title=Filter_(%E9%AB%98%E9%98%B6%E5%87%BD%E6%95%B0)&action=edit&redlink=1)和[reduce](https://baike.tw.lvfukeji.com/w/index.php?title=Fold_(%E9%AB%98%E9%98%B6%E5%87%BD%E6%95%B0)&action=edit&redlink=1)[15]。Python 1.4增加了受Modula-3启发的关键字参数和对复数)的内建支持,还包含采取名字修饰的一种基本形式的数据隐藏&action=edit&redlink=1)[16] - 2000年10月 发布Python 2.0,介入了列表推导式,这是从函数式编程语言SETL和Haskell中引入的。它还增加了基于环检测的垃圾收集系统),并且支持Unicode[17]。Python 2.1支持了嵌套作用域,就像其他静态作用域语言一样[18]。Python 2.2受Icon)启发而增加了生成器)[19]。Python 2.4加入了集合&action=edit&redlink=1)数据类型。Python 2.5加入了
with语句。 - 2008年12月 发布Python 3.0版本,它对语言做了较大修订而不能完全后向兼容[20]。Python 3发行包括了
2to3实用工具,它(至少部分的)自动将Python 2代码转换成Python 3代码[21]。
02Python语言特性
03Python术语对照
>>>
交互式终端中默认的 Python 提示符。往往会显示于能以交互方式在解释器里执行的样例代码之前。
...
可以是指:
- 交互式终端中输入特殊代码行时默认的 Python 提示符,包括:缩进的代码块,成对的分隔符之内(圆括号、方括号、花括号或三重引号),或是指定一个装饰器之后。
Ellipsis内置常量。
2to3
一个将 Python 2.x 代码转换为 Python 3.x 代码的工具,能够处理大部分通过解析源码并遍历解析树可检测到的不兼容问题。
2to3 包含在标准库中,模块名为lib2to3;并提供一个独立入口点Tools/scripts/2to3。参见 2to3 - 自动将 Python 2 代码转为 Python 3 代码。abstract base class — 抽象基类
抽象基类简称 ABC,是对 duck-typing 的补充,它提供了一种定义接口的新方式,相比之下其他技巧例如
hasattr()显得过于笨拙或有微妙错误(例如使用 魔术方法)。ABC 引入了虚拟子类,这种类并非继承自其他类,但却仍能被isinstance()和issubclass()所认可;详见abc模块文档。Python 自带许多内置的 ABC 用于实现数据结构(在collections.abc模块中)、数字(在numbers模块中)、流(在io模块中)、导入查找器和加载器(在importlib.abc模块中)。你可以使用abc模块来创建自己的 ABC。annotation — 标注
关联到某个变量、类属性、函数形参或返回值的标签,被约定作为 type hint 来使用。
局部变量的标注在运行时不可访问,但全局变量、类属性和函数的标注会分别存放模块、类和函数的__annotations__特殊属性中。
参见 variable annotation、function annotation、PEP 484 和 PEP 526,对此功能均有介绍。argument — 参数
关键字参数: 在函数调用中前面带有标识符(例如
name=)或者作为包含在前面带有**的字典里的值传入。举例来说,3和5在以下对complex()的调用中均属于关键字参数:
complex(real=3, imag=5)
complex(**{‘real’: 3, ‘imag’: 5})
- 位置参数: 不属于关键字参数的参数。位置参数可出现于参数列表的开头以及/或者作为前面带有
*的 iterable 里的元素被传入。举例来说,3和5在以下调用中均属于位置参数:
complex(3, 5)
complex((3, 5))
参数会被赋值给函数体中对应的局部变量。有关赋值规则参见 调用 一节。根据语法,任何表达式都可用来表示一个参数;最终算出的值会被赋给对应的局部变量。
另参见 parameter 术语表条目,常见问题中 参数与形参的区别 以及 [*PEP 362](https://www.python.org/dev/peps/pep-0362)。
asynchronous context manager — 异步上下文管理器
此种对象通过定义 __aenter__() 和 __aexit__() 方法来对 async with 语句中的环境进行控制。由 PEP 492 引入。
asynchronous generator — 异步生成器
返回值为 asynchronous generator iterator 的函数。它与使用 async def 定义的协程函数很相似,不同之处在于它包含 yield 表达式以产生一系列可在 async for 循环中使用的值。
此术语通常是指异步生成器函数,但在某些情况下则可能是指 异步生成器迭代器。如果需要清楚表达具体含义,请使用全称以避免歧义。
一个异步生成器函数可能包含 await 表达式或者 async for 以及 async with 语句。
asynchronous generator iterator — 异步生成器迭代器
asynchronous generator 函数所创建的对象。
此对象属于 asynchronous iterator,当使用 __anext__() 方法调用时会返回一个可等待对象来执行异步生成器函数的代码直到下一个 yield 表达式。
每个 yield 会临时暂停处理,记住当前位置执行状态 (包括局部变量和挂起的 try 语句)。当该 异步生成器迭代器 与其他 __anext__() 返回的可等待对象有效恢复时,它会从离开位置继续执行。参见 PEP 492 和 PEP 525。
asynchronous iterable — 异步可迭代对象
可在 async for 语句中被使用的对象。必须通过它的 __aiter__() 方法返回一个 asynchronous iterator。由 PEP 492 引入。
asynchronous iterator — 异步迭代器
实现了 __aiter__() 和 __anext__() 方法的对象。__anext__ 必须返回一个 awaitable 对象。async for 会处理异步迭代器的 __anext__() 方法所返回的可等待对象,直到其引发一个 StopAsyncIteration 异常。由 PEP 492 引入。
attribute — 属性
关联到一个对象的值,可以使用点号表达式通过其名称来引用。例如,如果一个对象 o 具有一个属性 a,就可以用 o.a 来引用它。
awaitable — 可等待对象
能在 await 表达式中使用的对象。可以是 coroutine 或是具有 __await__() 方法的对象。参见 PEP 492。
BDFL
“终身仁慈独裁者”的英文缩写,即 Guido van Rossum,Python 的创造者。
binary file — 二进制文件
file object 能够读写 字节类对象。二进制文件的例子包括以二进制模式('rb', 'wb' or 'rb+')打开的文件、sys.stdin.buffer、sys.stdout.buffer 以及 io.BytesIO 和 gzip.GzipFile 的实例。
另请参见 text file 了解能够读写 str 对象的文件对象。
bytes-like object — 字节类对象
支持 缓冲协议 并且能导出 C-contiguous 缓冲的对象。这包括所有 bytes、bytearray 和 array.array 对象,以及许多普通 memoryview 对象。字节类对象可在多种二进制数据操作中使用;这些操作包括压缩、保存为二进制文件以及通过套接字发送等。
某些操作需要可变的二进制数据。这种对象在文档中常被称为“可读写字节类对象”。可变缓冲对象的例子包括 bytearray 以及 bytearray 的 memoryview。其他操作要求二进制数据存放于不可变对象 (“只读字节类对象”);这种对象的例子包括 bytes 以及 bytes 对象的 memoryview。
bytecode — 字节码
Python 源代码会被编译为字节码,即 CPython 解释器中表示 Python 程序的内部代码。字节码还会缓存在 .pyc 文件中,这样第二次执行同一文件时速度更快(可以免去将源码重新编译为字节码)。这种 “中间语言” 运行在根据字节码执行相应机器码的 virtual machine 之上。请注意不同 Python 虚拟机上的字节码不一定通用,也不一定能在不同 Python 版本上兼容。
字节码指令列表可以在 dis 模块 的文档中查看。
callback — 回调
一个作为参数被传入以用以在未来的某个时刻被调用的子例程函数。
class — 类
用来创建用户定义对象的模板。类定义通常包含对该类的实例进行操作的方法定义。
class variable — 类变量
在类中定义的变量,并且仅限在类的层级上修改 (而不是在类的实例中修改)。
coercion — 强制类型转换
在包含两个相同类型参数的操作中,一种类型的实例隐式地转换为另一种类型。例如,int(3.15) 是将原浮点数转换为整型数 3,但在 3+4.5 中,参数的类型不一致(一个是 int, 一个是 float),两者必须转换为相同类型才能相加,否则将引发 TypeError。如果没有强制类型转换机制,程序员必须将所有可兼容参数归一化为相同类型,例如要写成 float(3)+4.5 而不是 3+4.5。
complex number — 复数
对普通实数系统的扩展,其中所有数字都被表示为一个实部和一个虚部的和。虚数是虚数单位(-1 的平方根)的实倍数,通常在数学中写为 i,在工程学中写为 j。Python 内置了对复数的支持,采用工程学标记方式;虚部带有一个 j 后缀,例如 3+1j。如果需要 math 模块内对象的对应复数版本,请使用 cmath,复数的使用是一个比较高级的数学特性。如果你感觉没有必要,忽略它们也几乎不会有任何问题。
context manager — 上下文管理器
在 with 语句中使用,通过定义 __enter__() 和 __exit__() 方法来控制环境状态的对象。参见 PEP 343。
context variable — 上下文变量
一种根据其所属的上下文可以具有不同的值的变量。 这类似于在线程局部存储中每个执行线程可以具有不同的变量值。 不过,对于上下文变量来说,一个执行线程中可能会有多个上下文,而上下文变量的主要用途是对并发异步任务中变量进行追踪。 参见 contextvars。
contiguous — 连续
一个缓冲如果是 C 连续 或 Fortran 连续 就会被认为是连续的。零维缓冲是 C 和 Fortran 连续的。在一维数组中,所有条目必须在内存中彼此相邻地排列,采用从零开始的递增索引顺序。在多维 C-连续数组中,当按内存地址排列时用最后一个索引访问条目时速度最快。但是在 Fortran 连续数组中则是用第一个索引最快。
coroutine — 协程
协程是子例程的更一般形式。 子例程可以在某一点进入并在另一点退出。 协程则可以在许多不同的点上进入、退出和恢复。 它们可通过 async def 语句来实现。 参见 PEP 492。
coroutine function — 协程函数
返回一个 coroutine 对象的函数。协程函数可通过 async def 语句来定义,并可能包含 await、async for 和 async with 关键字。这些特性是由 PEP 492 引入的。
CPython
Python 编程语言的规范实现,在 python.org 上发布。”CPython” 一词用于在必要时将此实现与其他实现例如 Jython 或 IronPython 相区别。
decorator — 装饰器
返回值为另一个函数的函数,通常使用 @wrapper 语法形式来进行函数变换。 装饰器的常见例子包括 classmethod() 和 staticmethod()。
装饰器语法只是一种语法糖,以下两个函数定义在语义上完全等价:
def f(…):
…
f = staticmethod(f)
@staticmethod
def f(…):
…
同的样概念也适用于类,但通常较少这样使用。有关装饰器的详情可参见 函数定义 和 类定义 的文档。
descriptor — 描述器
任何定义了 __get__(), __set__() 或 __delete__() 方法的对象。当一个类属性为描述器时,它的特殊绑定行为就会在属性查找时被触发。通常情况下,使用 a.b 来获取、设置或删除一个属性时会在 a 的类字典中查找名称为 b 的对象,但如果 b 是一个描述器,则会调用对应的描述器方法。理解描述器的概念是更深层次理解 Python 的关键,因为这是许多重要特性的基础,包括函数、方法、属性、类方法、静态方法以及对超类的引用等等。
有关描述符的方法的详情可参看 实现描述器。
dictionary — 字典
一个关联数组,其中的任意键都映射到相应的值。键可以是任何具有 __hash__() 和 __eq__() 方法的对象。在 Perl 语言中称为 hash。
dictionary view — 字典视图
从 dict.keys(), dict.values() 和 dict.items() 返回的对象被称为字典视图。它们提供了字典条目的一个动态视图,这意味着当字典改变时,视图也会相应改变。要将字典视图强制转换为真正的列表,可使用 list(dictview)。参见 字典视图对象。
docstring — 文档字符串
作为类、函数或模块之内的第一个表达式出现的字符串字面值。它在代码执行时会被忽略,但会被解释器识别并放入所在类、函数或模块的 __doc__ 属性中。由于它可用于代码内省,因此是对象存放文档的规范位置。
duck-typing — 鸭子类型
指一种编程风格,它并不依靠查找对象类型来确定其是否具有正确的接口,而是直接调用或使用其方法或属性(“看起来像鸭子,叫起来也像鸭子,那么肯定就是鸭子。”)由于强调接口而非特定类型,设计良好的代码可通过允许多态替代来提升灵活性。鸭子类型避免使用 type() 或 isinstance() 检测。(但要注意鸭子类型可以使用 抽象基类 作为补充。) 而往往会采用 hasattr() 检测或是 EAFP 编程。
EAFP
“求原谅比求许可更容易”的英文缩写。这种 Python 常用代码编写风格会假定所需的键或属性存在,并在假定错误时捕获异常。这种简洁快速风格的特点就是大量运用 try 和 except 语句。于其相对的则是所谓 LBYL 风格,常见于 C 等许多其他语言。
expression — 表达式
可以求出某个值的语法单元。 换句话说,一个表达式就是表达元素例如字面值、名称、属性访问、运算符或函数调用的汇总,它们最终都会返回一个值。 与许多其他语言不同,并非所有语言构件都是表达式。 还存在不能被用作表达式的 statement,例如 while。 赋值也是属于语句而非表达式。
extension module — 扩展模块
以 C 或 C++ 编写的模块,使用 Python 的 C API 来与语言核心以及用户代码进行交互。
f-string — f-字符串
带有 'f' 或 'F' 前缀的字符串字面值通常被称为“f-字符串”即 格式化字符串字面值 的简写。参见 PEP 498。
file object — 文件对象
对外提供面向文件 API 以使用下层资源的对象(带有 read() 或 write() 这样的方法)。根据其创建方式的不同,文件对象可以处理对真实磁盘文件,对其他类型存储,或是对通讯设备的访问(例如标准输入/输出、内存缓冲区、套接字、管道等等)。文件对象也被称为 文件类对象 或 流。
实际上共有三种类别的文件对象: 原始 二进制文件, 缓冲 二进制文件 以及 文本文件。它们的接口定义均在 io 模块中。创建文件对象的规范方式是使用 open() 函数。
file-like object — 文件类对象
file object 的同义词。
finder — 查找器
一种会尝试查找被导入模块的 loader 的对象。
从 Python 3.3 起存在两种类型的查找器: 元路径查找器 配合 sys.meta_path 使用,以及 path entry finders 配合 sys.path_hooks 使用。
更多详情可参见 PEP 302, PEP 420 和 PEP 451。
floor division — 向下取整除法
向下舍入到最接近的整数的数学除法。向下取整除法的运算符是 // 。例如,表达式 11 // 4 的计算结果是 2 ,而与之相反的是浮点数的真正除法返回 2.75 。注意 (-11) // 4 会返回 -3 因为这是 -2.75 向下 舍入得到的结果。见 PEP 238 。
function — 函数
可以向调用者返回某个值的一组语句。还可以向其传入零个或多个 参数 并在函数体执行中被使用。另见 parameter, method 和 函数定义 等节。
function annotation — 函数标注
即针对函数形参或返回值的 annotation 。
函数标注通常用于 类型提示:例如以下函数预期接受两个 int 参数并预期返回一个 int 值:
def sum_two_numbers(a: int, b: int) -> int:
return a + b
函数标注语法的详解见 函数定义 一节。
请参看 variable annotation 和 PEP 484 对此功能的描述。
future
一种伪模块,可被程序员用来启用与当前解释器不兼容的新语言特性。
通过导入 __future__ 模块并对其中的变量求值,你可以查看新特性何时首次加入语言以及何时成为默认:
>>>
>>> import future
>>> future.division
_Feature((2, 2, 0, ‘alpha’, 2), (3, 0, 0, ‘alpha’, 0), 8192)
garbage collection — 垃圾回收
释放不再被使用的内存空间的过程。Python 是通过引用计数和一个能够检测和打破循环引用的循环垃圾回收器来执行垃圾回收的。可以使用 gc 模块来控制垃圾回收器。
generator — 生成器
返回一个 generator iterator 的函数。它看起来很像普通函数,不同点在于其包含 yield 表达式以便产生一系列值供给 for-循环使用或是通过 next() 函数逐一获取。
通常是指生成器函数,但在某些情况下也可能是指 生成器迭代器。如果需要清楚表达具体含义,请使用全称以避免歧义。
generator iterator — 生成器迭代器
generator 函数所创建的对象。
每个 yield 会临时暂停处理,记住当前位置执行状态(包括局部变量和挂起的 try 语句)。当该 生成器迭代器 恢复时,它会从离开位置继续执行(这与每次调用都从新开始的普通函数差别很大)。
generator expression — 生成器表达式
返回一个迭代器的表达式。 它看起来很像普通表达式后面带有定义了一个循环变量、范围的 for 子句,以及一个可选的 if 子句。 以下复合表达式会为外层函数生成一系列值:
>>>
>>> sum(ii for i *in range(10)) # sum of squares 0, 1, 4, … 81
285
generic function — 泛型函数
为不同的类型实现相同操作的多个函数所组成的函数。在调用时会由调度算法来确定应该使用哪个实现。
另请参见 single dispatch 术语表条目、functools.singledispatch() 装饰器以及 PEP 443。
GIL
参见 global interpreter lock。
global interpreter lock — 全局解释器锁
CPython 解释器所采用的一种机制,它确保同一时刻只有一个线程在执行 Python bytecode。此机制通过设置对象模型(包括 dict 等重要内置类型)针对并发访问的隐式安全简化了 CPython 实现。给整个解释器加锁使得解释器多线程运行更方便,其代价则是牺牲了在多处理器上的并行性。
不过,某些标准库或第三方库的扩展模块被设计为在执行计算密集型任务如压缩或哈希时释放 GIL。此外,在执行 I/O 操作时也总是会释放 GIL。
创建一个(以更精细粒度来锁定共享数据的)“自由线程”解释器的努力从未获得成功,因为这会牺牲在普通单处理器情况下的性能。据信克服这种性能问题的措施将导致实现变得更复杂,从而更难以维护。
hash-based pyc — 基于哈希的 pyc
使用对应源文件的哈希值而非最后修改时间来确定其有效性的字节码缓存文件。 参见 已缓存字节码的失效。
hashable — 可哈希
一个对象的哈希值如果在其生命周期内绝不改变,就被称为 可哈希 (它需要具有 __hash__() 方法),并可以同其他对象进行比较(它需要具有 __eq__() 方法)。可哈希对象必须具有相同的哈希值比较结果才会相同。
可哈希性使得对象能够作为字典键或集合成员使用,因为这些数据结构要在内部使用哈希值。
大多数 Python 中的不可变内置对象都是可哈希的;可变容器(例如列表或字典)都不可哈希;不可变容器(例如元组和 frozenset)仅当它们的元素均为可哈希时才是可哈希的。 用户定义类的实例对象默认是可哈希的。 它们在比较时一定不相同(除非是与自己比较),它们的哈希值的生成是基于它们的 id()。
IDLE
Python 的 IDE,“集成开发与学习环境”的英文缩写。是 Python 标准发行版附带的基本编辑器和解释器环境。
immutable — 不可变
具有固定值的对象。不可变对象包括数字、字符串和元组。这样的对象不能被改变。如果必须存储一个不同的值,则必须创建新的对象。它们在需要常量哈希值的地方起着重要作用,例如作为字典中的键。
import path — 导入路径
由多个位置(或 路径条目)组成的列表,会被模块的 path based finder 用来查找导入目标。在导入时,此位置列表通常来自 sys.path,但对次级包来说也可能来自上级包的 __path__ 属性。
importing — 导入
令一个模块中的 Python 代码能为另一个模块中的 Python 代码所使用的过程。
importer — 导入器
查找并加载模块的对象;此对象既属于 finder 又属于 loader。
interactive — 交互
Python 带有一个交互式解释器,即你可以在解释器提示符后输入语句和表达式,立即执行并查看其结果。只需不带参数地启动 python 命令(也可以在你的计算机开始菜单中选择相应菜单项)。在测试新想法或检验模块和包的时候用这种方式会非常方便(请记得使用 help(x))。
interpreted — 解释型
Python 一是种解释型语言,与之相对的是编译型语言,虽然两者的区别由于字节码编译器的存在而会有所模糊。这意味着源文件可以直接运行而不必显式地创建可执行文件再运行。解释型语言通常具有比编译型语言更短的开发/调试周期,但是其程序往往运行得更慢。参见 interactive。
interpreter shutdown — 解释器关闭
当被要求关闭时,Python 解释器将进入一个特殊运行阶段并逐步释放所有已分配资源,例如模块和各种关键内部结构等。它还会多次调用 垃圾回收器。这会触发用户定义析构器或弱引用回调中的代码执行。在关闭阶段执行的代码可能会遇到各种异常,因为其所依赖的资源已不再有效(常见的例子有库模块或警告机制等)。
解释器需要关闭的主要原因有 __main__ 模块或所运行的脚本已完成执行。
iterable — 可迭代对象
能够逐一返回其成员项的对象。 可迭代对象的例子包括所有序列类型 (例如 list, str 和 tuple) 以及某些非序列类型例如 dict, 文件对象 以及定义了 __iter__() 方法或是实现了 序列 语义的 __getitem__() 方法的任意自定义类对象。
可迭代对象被可用于 for 循环以及许多其他需要一个序列的地方(zip()、map() …)。当一个可迭代对象作为参数传给内置函数 iter() 时,它会返回该对象的迭代器。这种迭代器适用于对值集合的一次性遍历。在使用可迭代对象时,你通常不需要调用 iter() 或者自己处理迭代器对象。for 语句会为你自动处理那些操作,创建一个临时的未命名变量用来在循环期间保存迭代器。参见 iterator、sequence 以及 generator。
iterator — 迭代器
用来表示一连串数据流的对象。重复调用迭代器的 __next__() 方法(或将其传给内置函数 next())将逐个返回流中的项。当没有数据可用时则将引发 StopIteration 异常。到这时迭代器对象中的数据项已耗尽,继续调用其 __next__() 方法只会再次引发 StopIteration 异常。迭代器必须具有 __iter__() 方法用来返回该迭代器对象自身,因此迭代器必定也是可迭代对象,可被用于其他可迭代对象适用的大部分场合。一个显著的例外是那些会多次重复访问迭代项的代码。容器对象(例如 list)在你每次向其传入 iter() 函数或是在 for 循环中使用它时都会产生一个新的迭代器。如果在此情况下你尝试用迭代器则会返回在之前迭代过程中被耗尽的同一迭代器对象,使其看起来就像是一个空容器。
更多信息可查看 迭代器类型。
key function — 键函数
键函数或称整理函数,是能够返回用于排序或排位的值的可调用对象。例如,locale.strxfrm() 可用于生成一个符合特定区域排序约定的排序键。
Python 中有许多工具都允许用键函数来控制元素的排位或分组方式。其中包括 min(), max(), sorted(), list.sort(), heapq.merge(), heapq.nsmallest(), heapq.nlargest() 以及 itertools.groupby()。
要创建一个键函数有多种方式。例如,str.lower() 方法可以用作忽略大小写排序的键函数。另外,键函数也可通过 lambda 表达式来创建,例如 lambda r: (r[0], r[2])。还有 operator 模块提供了三个键函数构造器:attrgetter()、itemgetter() 和 methodcaller()。请查看 如何排序 一节以获取创建和使用键函数的示例。
keyword argument — 关键字参数
参见 argument。
lambda
由一个单独 expression 构成的匿名内联函数,表达式会在调用时被求值。创建 lambda 函数的句法为 lambda [parameters]: expression
LBYL
“先查看后跳跃”的英文缩写。这种代码编写风格会在进行调用或查找之前显式地检查前提条件。此风格与 EAFP 方式恰成对比,其特点是大量使用 if 语句。
在多线程环境中,LBYL 方式会导致“查看”和“跳跃”之间发生条件竞争风险。例如,以下代码 if key in mapping: return mapping[key] 可能由于在检查操作之后其他线程从 mapping 中移除了 key 而出错。这种问题可通过加锁或使用 EAFP 方式来解决。
list — 列表
Python 内置的一种 sequence。虽然名为列表,但更类似于其他语言中的数组而非链接列表,因为访问元素的时间复杂度为 O(1)。
list comprehension — 列表推导式
处理一个序列中的所有或部分元素并返回结果列表的一种紧凑写法。result = ['{:#04x}'.format(x) for x in range(256) if x % 2 == 0] 将生成一个 0 到 255 范围内的十六进制偶数对应字符串(0x..)的列表。其中 if 子句是可选的,如果省略则 range(256) 中的所有元素都会被处理。
loader — 加载器
负责加载模块的对象。它必须定义名为 load_module() 的方法。加载器通常由一个 finder 返回。详情参见 PEP 302,对于 abstract base class 可参见 importlib.abc.Loader。
magic method — 魔术方法
special method 的非正式同义词 。
mapping — 映射
一种支持任意键查找并实现了 Mapping 或 MutableMapping 抽象基类 中所规定方法的容器对象。 此类对象的例子包括 dict, collections.defaultdict, collections.OrderedDict 以及 collections.Counter。
meta path finder — 元路径查找器sys.meta_path 的搜索所返回的 finder。元路径查找器与 path entry finders 存在关联但并不相同。
请查看 importlib.abc.MetaPathFinder 了解元路径查找器所实现的方法。
metaclass — 元类
一种用于创建类的类。类定义包含类名、类字典和基类列表。元类负责接受上述三个参数并创建相应的类。大部分面向对象的编程语言都会提供一个默认实现。Python 的特别之处在于可以创建自定义元类。大部分用户永远不需要这个工具,但当需要出现时,元类可提供强大而优雅的解决方案。它们已被用于记录属性访问日志、添加线程安全性、跟踪对象创建、实现单例,以及其他许多任务。
更多详情参见 元类。
method — 方法
在类内部定义的函数。如果作为该类的实例的一个属性来调用,方法将会获取实例对象作为其第一个 argument (通常命名为 self)。参见 function 和 nested scope。
method resolution order — 方法解析顺序
方法解析顺序就是在查找成员时搜索全部基类所用的先后顺序。请查看 Python 2.3 方法解析顺序 了解自 2.3 版起 Python 解析器所用相关算法的详情。
module — 模块
此对象是 Python 代码的一种组织单位。各模块具有独立的命名空间,可包含任意 Python 对象。模块可通过 importing 操作被加载到 Python 中。
另见 package。
module spec — 模块规格
一个命名空间,其中包含用于加载模块的相关导入信息。是 importlib.machinery.ModuleSpec 的实例。
MRO
参见 method resolution order。
mutable — 可变
可变对象可以在其 id() 保持固定的情况下改变其取值。另请参见 immutable。
named tuple — 具名元组
术语“具名元组”可用于任何继承自元组,并且其中的可索引元素还能使用名称属性来访问的类型或类。 这样的类型或类还可能拥有其他特性。
有些内置类型属于具名元组,包括 time.localtime() 和 os.stat() 的返回值。 另一个例子是 sys.float_info:
>>>
>>> sys.floatinfo[1] # indexed access
1024
>>> sys.float_info.max_exp # named field access
1024
>>> isinstance(sys.float_info, tuple) # kind of tuple_
True
有些具名元组是内置类型(例如上面的例子)。 此外,具名元组还可通过常规类定义从 tuple 继承并定义名称字段的方式来创建。 这样的类可以手工编写,或者使用工厂函数 collections.namedtuple() 创建。 后一种方式还会添加一些手工编写或内置具名元组所没有的额外方法。
namespace — 命名空间
命名空间是存放变量的场所。命名空间有局部、全局和内置的,还有对象中的嵌套命名空间(在方法之内)。命名空间通过防止命名冲突来支持模块化。例如,函数 builtins.open 与 os.open() 可通过各自的命名空间来区分。命名空间还通过明确哪个模块实现那个函数来帮助提高可读性和可维护性。例如,random.seed() 或 itertools.islice() 这种写法明确了这些函数是由 random 与 itertools 模块分别实现的。
namespace package — 命名空间包
PEP 420 所引入的一种仅被用作子包的容器的 package,命名空间包可以没有实体表示物,其描述方式与 regular package 不同,因为它们没有 __init__.py 文件。
另可参见 module。
nested scope — 嵌套作用域
在一个定义范围内引用变量的能力。例如,在另一函数之内定义的函数可以引用前者的变量。请注意嵌套作用域默认只对引用有效而对赋值无效。局部变量的读写都受限于最内层作用域。类似的,全局变量的读写则作用于全局命名空间。通过 nonlocal 关键字可允许写入外层作用域。
new-style class — 新式类
对于目前已被应于所有类对象的类形式的旧称谓。在早先的 Python 版本中,只有新式类能够使用 Python 新增的更灵活特性,例如 __slots__、描述符、特征属性、__getattribute__()、类方法和静态方法等。
object — 对象
任何具有状态(属性或值)以及预定义行为(方法)的数据。object 也是任何 new-style class 的最顶层基类名。
package — 包
一种可包含子模块或递归地包含子包的 Python module。从技术上说,包是带有 __path__ 属性的 Python 模块。
另参见 regular package 和 namespace package。
parameter — 形参
function (或方法)定义中的命名实体,它指定函数可以接受的一个 argument (或在某些情况下,多个实参)。有五种形参:
def func(foo, bar=None): …
- positional-only:仅限位置,指定一个只能通过位置传入的参数。 仅限位置形参可通过在函数定义的形参列表中它们之后包含一个
/字符来定义,例如下面的 posonly1 和 posonly2:
def func(posonly1, posonly2, /, positional_or_keyword): …
- keyword-only:仅限关键字,指定一个只能通过关键字传入的参数。仅限关键字形参可通过在函数定义的形参列表中包含单个可变位置形参或者在多个可变位置形参之前放一个
*来定义,例如下面的 kw_only1 和 kw_only2:
def func(arg, *, kw_only1, kw_only2): …
- var-positional:可变位置,指定可以提供由一个任意数量的位置参数构成的序列(附加在其他形参已接受的位置参数之后)。这种形参可通过在形参名称前加缀
*来定义,例如下面的 args:
def func(args, *kwargs): …
- var-keyword:可变关键字,指定可以提供任意数量的关键字参数(附加在其他形参已接受的关键字参数之后)。这种形参可通过在形参名称前加缀
**来定义,例如上面的 kwargs。
形参可以同时指定可选和必选参数,也可以为某些可选参数指定默认值。
另参见 argument 术语表条目、参数与形参的区别 中的常见问题、inspect.Parameter 类、函数定义 一节以及 PEP 362。
path entry — 路径入口
import path 中的一个单独位置,会被 path based finder 用来查找要导入的模块。
path entry finder — 路径入口查找器
任一可调用对象使用 sys.path_hooks (即 path entry hook) 返回的 finder,此种对象能通过 path entry 来定位模块。
请参看 importlib.abc.PathEntryFinder 以了解路径入口查找器所实现的各个方法。
path entry hook — 路径入口钩子
一种可调用对象,在知道如何查找特定 path entry 中的模块的情况下能够使用 sys.path_hook 列表返回一个 path entry finder。
path based finder — 基于路径的查找器
默认的一种 元路径查找器,可在一个 import path 中查找模块。
path-like object — 路径类对象
代表一个文件系统路径的对象。类路径对象可以是一个表示路径的 str 或者 bytes 对象,还可以是一个实现了 os.PathLike 协议的对象。一个支持 os.PathLike 协议的对象可通过调用 os.fspath() 函数转换为 str 或者 bytes 类型的文件系统路径;os.fsdecode() 和 os.fsencode() 可被分别用来确保获得 str 或 bytes 类型的结果。此对象是由 PEP 519 引入的。
PEP
“Python 增强提议”的英文缩写。一个 PEP 就是一份设计文档,用来向 Python 社区提供信息,或描述一个 Python 的新增特性及其进度或环境。PEP 应当提供精确的技术规格和所提议特性的原理说明。
PEP 应被作为提出主要新特性建议、收集社区对特定问题反馈以及为必须加入 Python 的设计决策编写文档的首选机制。PEP 的作者有责任在社区内部建立共识,并应将不同意见也记入文档。
参见 PEP 1。
portion — 部分
构成一个命名空间包的单个目录内文件集合(也可能存放于一个 zip 文件内),具体定义见 PEP 420。
positional argument — 位置参数
参见 argument。
provisional API — 暂定 API
暂定 API 是指被有意排除在标准库的向后兼容性保证之外的应用编程接口。虽然此类接口通常不会再有重大改变,但只要其被标记为暂定,就可能在核心开发者确定有必要的情况下进行向后不兼容的更改(甚至包括移除该接口)。此种更改并不会随意进行 — 仅在 API 被加入之前未考虑到的严重基础性缺陷被发现时才可能会这样做。
即便是对暂定 API 来说,向后不兼容的更改也会被视为“最后的解决方案” —— 任何问题被确认时都会尽可能先尝试找到一种向后兼容的解决方案。
这种处理过程允许标准库持续不断地演进,不至于被有问题的长期性设计缺陷所困。详情见 PEP 411。
provisional package — 暂定包
参见 provisional API。
Python 3000
Python 3.x 发布路线的昵称(这个名字在版本 3 的发布还遥遥无期的时候就已出现了)。有时也被缩写为“Py3k”。
Pythonic
指一个思路或一段代码紧密遵循了 Python 语言最常用的风格和理念,而不是使用其他语言中通用的概念来实现代码。例如,Python 的常用风格是使用 for 语句循环来遍历一个可迭代对象中的所有元素。许多其他语言没有这样的结构,因此不熟悉 Python 的人有时会选择使用一个数字计数器:
for i in range(len(food)):
print(food[i])
而相应的更简洁更 Pythonic 的方法是这样的:
for piece in food:
print(piece)
qualified name — 限定名称
一个以点号分隔的名称,显示从模块的全局作用域到该模块中定义的某个类、函数或方法的“路径”,相关定义见 PEP 3155。对于最高层级的函数和类,限定名称与对象名称一致:
>>>
>>> class C:
… class D:
… def meth(self):
… pass
…
>>> C.qualname
‘C’
>>> C.D.qualname
‘C.D’
>>> C.D.meth.qualname
‘C.D.meth’
当被用于引用模块时,完整限定名称 意为标示该模块的以点号分隔的整个路径,其中包含其所有的父包,例如 email.mime.text:
>>>
>>> import email.mime.text
>>> email.mime.text.name
‘email.mime.text’
reference count — 引用计数
对特定对象的引用的数量。当一个对象的引用计数降为零时,所分配资源将被释放。引用计数对 Python 代码来说通常是不可见的,但它是 CPython 实现的一个关键元素。sys 模块定义了一个 getrefcount() 函数,程序员可调用它来返回特定对象的引用计数。
regular package — 常规包
传统型的 package,例如包含有一个 __init__.py 文件的目录。
另参见 namespace package。
slots
一种写在类内部的声明,通过预先声明实例属性等对象并移除实例字典来节省内存。虽然这种技巧很流行,但想要用好却并不容易,最好是只保留在少数情况下采用,例如极耗内存的应用程序,并且其中包含大量实例。
sequence — 序列
一种 iterable,它支持通过 __getitem__() 特殊方法来使用整数索引进行高效的元素访问,并定义了一个返回序列长度的 __len__() 方法。内置的序列类型有 list、str、tuple 和 bytes。注意虽然 dict 也支持 __getitem__() 和 __len__(),但它被认为属于映射而非序列,因为它查找时使用任意的 immutable 键而非整数。collections.abc.Sequence 抽象基类定义了一个更丰富的接口,它超越了 __getitem__() 和 __len__(),添加了 count(), index(), __contains__() 和 __reversed__() 。 可以使用 register() 显式注册实现此扩展接口的类型。
single dispatch — 单分派
一种 generic function 分派形式,其实现是基于单个参数的类型来选择的。
slice — 切片
通常只包含了特定 sequence 的一部分的对象。切片是通过使用下标标记来创建的,在 [] 中给出几个以冒号分隔的数字,例如 variable_name[1:3:5]。方括号(下标)标记在内部使用 slice 对象。
special method — 特殊方法
一种由 Python 隐式调用的方法,用来对某个类型执行特定操作例如相加等等。这种方法的名称的首尾都为双下划线。特殊方法的文档参见 特殊方法名称。
statement — 语句
语句是程序段(一个代码“块”)的组成单位。一条语句可以是一个 expression 或某个带有关键字的结构,例如 if、while 或 for。
text encoding — 文本编码
text file — 文本文件
一种能够读写 str 对象的 file object。通常一个文本文件实际是访问一个面向字节的数据流并自动处理 text encoding。文本文件的例子包括以文本模式('r' 或 'w')打开的文件、sys.stdin、sys.stdout 以及 io.StringIO 的实例。
另请参看 binary file 了解能够读写 字节类对象 的文件对象。
triple-quoted string — 三引号字符串
首尾各带三个连续双引号(”)或者单引号(’)的字符串。它们在功能上与首尾各用一个引号标注的字符串没有什么不同,但是有多种用处。它们允许你在字符串内包含未经转义的单引号和双引号,并且可以跨越多行而无需使用连接符,在编写文档字符串时特别好用。
type — 类型
类型决定一个 Python 对象属于什么种类;每个对象都具有一种类型。要知道对象的类型,可以访问它的 __class__ 属性,或是通过 type(obj) 来获取。
type alias — 类型别名
一个类型的同义词,创建方式是把类型赋值给特定的标识符。
类型别名的作用是简化 类型提示。例如:
from typing import List, Tuple
def remove_gray_shades(
colors: List[Tuple[int, int, int]]) -> List[Tuple[int, int, int]]:
pass
可以这样提高可读性:
from typing import List, Tuple
Color = Tuple[int, int, int]
def remove_gray_shades(colors: List[Color]) -> List[Color]:
pass
参见 typing 和 PEP 484,其中有对此功能的详细描述。
type hint — 类型提示
annotation 为变量、类属性、函数的形参或返回值指定预期的类型。
类型提示属于可选项,Python 不要求提供,但其可对静态类型分析工具起作用,并可协助 IDE 实现代码补全与重构。
全局变量、类属性和函数的类型提示可以使用 typing.get_type_hints() 来访问,但局部变量则不可以。
参见 typing 和 PEP 484,其中有对此功能的详细描述。
universal newlines — 通用换行
一种解读文本流的方式,将以下所有符号都识别为行结束标志:Unix 的行结束约定 '\n'、Windows 的约定 '\r\n' 以及旧版 Macintosh 的约定 '\r'。参见 PEP 278 和 PEP 3116 和 bytes.splitlines() 了解更多用法说明。
variable annotation — 变量标注
对变量或类属性的 annotation。
在标注变量或类属性时,还可选择为其赋值:
class C:
field: ‘annotation’
变量标注通常被用作 类型提示:例如以下变量预期接受 int 类型的值:
count: int = 0
变量标注语法的详细解释见 带标注的赋值语句 一节。
请参看 function annotation、PEP 484 和 PEP 526,其中对此功能有详细描述。
virtual environment — 虚拟环境
一种采用协作式隔离的运行时环境,允许 Python 用户和应用程序在安装和升级 Python 分发包时不会干扰到同一系统上运行的其他 Python 应用程序的行为。
另参见 venv。
virtual machine — 虚拟机
一台完全通过软件定义的计算机。Python 虚拟机可执行字节码编译器所生成的 bytecode。
Zen of Python — Python 之禅
列出 Python 设计的原则与哲学,有助于理解与使用这种语言。查看其具体内容可在交互模式提示符中输入 “import this“。
04Python常见问题
一般问题
Python 有没有提供断点与单步调试等功能的,源码层次的调试器?
有的。
以下介绍了一些 Python 的调试器,内置函数 breakpoint() 允许你使用其中的任何一种。
pdb 模块是一个简单但是够用的控制台模式 Python 调试器。 它是标准 Python 库的一部分,并且 已收录于库参考手册。 你也可以通过使用 pdb 代码作为样例来编写你自己的调试器。
作为标准 Python 发行版附带组件的 IDLE 交互式环境(通常位于 Tools/scripts/idle)中包含一个图形化的调试器。
PythonWin 是一个包含有基于 pdb 的 GUI 调试器的 Python IDE。 Pythonwin 调试器会为断点加上颜色,并具有许多很棒的特性,例如也可以非 Pythonwin 程序。 Pythonwin 是 Python for Windows Extensions 项目的一部分,也是 ActivePython 发行版的一部分(参见 https://www.activestate.com/activepython)。
Eric 是一个基于PyQt和Scintilla编辑组件构建的IDE。
Pydb是标准Python调试器pdb的一个版本,经过修改后可与DDD(数据显示调试器)一起使用,DDD是一种流行的图形化调试器前端。 Pydb可以在 http://bashdb.sourceforge.net/pydb/ 找到,DDD可以在 https://www.gnu.org/software/ddd 找到。
有许多商业Python IDE包括图形调试器。他们包括:
- Wing IDE (https://wingware.com/)
- Komodo IDE (https://komodoide.com/)
- PyCharm (https://www.jetbrains.com/pycharm/)
是否有能帮助寻找漏洞或执行静态分析的工具?
有的。
Pylint 和 Pyflakes 可进行基本检查来帮助你尽早捕捉漏洞。
静态类型检查器,例如 Mypy 、 Pyre 和 Pytype 可以检查Python源代码中的类型提示。我如何能够通过一个 Python 脚本创建一个独立运行的二进制文件?
如果你想要的只是一个独立的程序,用户可以下载和运行而不必先安装Python发行版,你就不需要将Python编译成C代码。有许多工具可以确定程序所需的模块集,并将这些模块与Python二进制文件绑定在一起以生成单个可执行文件。
一种是使用冻结工具,它包含在Python源代码树Tools/freeze中。它将Python字节代码转换为C数组;一个C编译器,你可以将所有模块嵌入到一个新程序中,然后将其与标准Python模块链接。
它的工作原理是递归扫描源代码以获取import语句(两种形式),并在标准Python路径和源目录(用于内置模块)中查找模块。 然后,它将用Python编写的模块的字节码转换为C代码(可以使用编组模块转换为代码对象的数组初始化器),并创建一个定制的配置文件,该文件仅包含程序中实际使用的内置模块。 然后,它编译生成的C代码并将其与Python解释器的其余部分链接,以形成一个独立的二进制文件,其行为与你的脚本完全相同。
显然, freeze 需要一个C编译器。有几个其他实用工具不需要。 一个是Thomas Heller的py2exe(仅限Windows)
另一个工具是 Anthony Tuininga 的 cx_Freeze。
是否有 Python 程序规范代码标准或风格指南?
有的。 请参阅标准库模块所要求的代码风格描述文档 PEP 8 。
历史常见问题
为什么Python使用缩进来分组语句?
Guido van Rossum 认为使用缩进进行分组非常优雅,并且大大提高了普通Python程序的清晰度。大多数人在一段时间后就学会并喜欢上这个功能。
由于没有开始/结束括号,因此解析器感知的分组与人类读者之间不会存在分歧。偶尔C程序员会遇到像这样的代码片段:
if (x <= y)
x++;
y—;
z++;
如果条件为真,则只执行 x++ 语句,但缩进会使你认为情况并非如此。即使是经验丰富的C程序员有时会长时间盯着它,想知道为什么即使 x > y , y 也在减少。
因为没有开始/结束括号,所以Python不太容易发生编码式冲突。在C中,括号可以放到许多不同的位置。如果您习惯于阅读和编写使用一种风格的代码,那么在阅读(或被要求编写)另一种风格时,您至少会感到有些不安。
许多编码风格将开始/结束括号单独放在一行上。这使得程序相当长,浪费了宝贵的屏幕空间,使得更难以对程序进行全面的了解。理想情况下,函数应该适合一个屏幕(例如,20—30行)。 20行Python可以完成比20行C更多的工作。这不仅仅是由于缺少开始/结束括号 — 缺少声明和高级数据类型也是其中的原因 — 但缩进基于语法肯定有帮助。
为什么简单的算术运算得到奇怪的结果?
为什么浮点计算不准确?
用户经常对这样的结果感到惊讶:
>>>
>>> 1.2 - 1.0
0.19999999999999996
并且认为这是 Python中的一个 bug。其实不是这样。这与 Python 关系不大,而与底层平台如何处理浮点数字关系更大。
CPython 中的 float 类型使用C语言的 double 类型进行存储。 float 对象的值是以固定的精度(通常为 53 位)存储的二进制浮点数,由于 Python 使用 C 操作,而后者依赖于处理器中的硬件实现来执行浮点运算。 这意味着就浮点运算而言,Python 的行为类似于许多流行的语言,包括 C 和 Java。
许多可以轻松地用十进制表示的数字不能用二进制浮点表示。例如,在输入以下语句后:
>>>
>>> x = 1.2
为 x 存储的值是与十进制的值 1.2 (非常接近) 的近似值,但不完全等于它。 在典型的机器上,实际存储的值是:
1.0011001100110011001100110011001100110011001100110011 (binary)
它对应于十进制数值:
1.1999999999999999555910790149937383830547332763671875 (decimal)
典型的 53 位精度为 Python 浮点数提供了 15-16 位小数的精度。
要获得更完整的解释,请参阅 Python 教程中的 浮点算术 一章。
为什么Python字符串是不可变的?
有几个优点。
一个是性能:知道字符串是不可变的,意味着我们可以在创建时为它分配空间,并且存储需求是固定不变的。这也是元组和列表之间区别的原因之一。
另一个优点是,Python 中的字符串被视为与数字一样“基本”。 任何动作都不会将值 8 更改为其他值,在 Python 中,任何动作都不会将字符串 “8” 更改为其他值。
为什么必须在方法定义和调用中显式使用“self”?
这个想法借鉴了 Modula-3 语言。 出于多种原因它被证明是非常有用的。
首先,更明显的显示出,使用的是方法或实例属性而不是局部变量。
阅读 self.x 或 self.meth() 可以清楚地表明,即使您不知道类的定义,也会使用实例变量或方法。在 C++ 中,可以通过缺少局部变量声明来判断(假设全局变量很少见或容易识别) —— 但是在 Python 中没有局部变量声明,所以必须查找类定义才能确定。 一些 C++ 和 Java 编码标准要求实例属性具有 m_ 前缀,因此这种显式性在这些语言中仍然有用。
其次,这意味着如果要显式引用或从特定类调用该方法,不需要特殊语法。
在 C++ 中,如果你想使用在派生类中重写基类中的方法,你必须使用 :: 运算符 — 在 Python 中你可以编写 baseclass.methodname(self, <argument list>)。 这对于 __init__() 方法非常有用,特别是在派生类方法想要扩展同名的基类方法,而必须以某种方式调用基类方法时。
最后,它解决了变量赋值的语法问题:
为了 Python 中的局部变量(根据定义!)在函数体中赋值的那些变量(并且没有明确声明为全局)赋值,就必须以某种方式告诉解释器一个赋值是为了分配一个实例变量而不是一个局部变量,它最好是通过语法实现的(出于效率原因)。 C++ 通过声明来做到这一点,但是 Python 没有声明,仅仅为了这个目的而引入它们会很可惜。 使用显式的 self.var 很好地解决了这个问题。 类似地,对于使用实例变量,必须编写 self.var 意味着对方法内部的非限定名称的引用不必搜索实例的目录。 换句话说,局部变量和实例变量存在于两个不同的命名空间中,您需要告诉 Python 使用哪个命名空间。
为什么不能在表达式中赋值?
自 Python 3.8 开始,你能做到的!
赋值表达式使用海象运算符 := 在表达式中为变量赋值:
while chunk := fp.read(200):
print(chunk)
请参阅 PEP 572 了解详情。
为什么Python对某些功能(例如list.index())使用方法来实现,而其他功能(例如len(List))使用函数实现?
正如Guido所说:
(a) 对于某些操作,前缀表示法比后缀更容易阅读 — 前缀(和中缀!)运算在数学中有着悠久的传统,就像在视觉上帮助数学家思考问题的记法。比较一下我们将 x(a+b) 这样的公式改写为 xa+x*b 的容易程度,以及使用原始OO符号做相同事情的笨拙程度。 (b) 当读到写有len(X)的代码时,就知道它要求的是某件东西的长度。这告诉我们两件事:结果是一个整数,参数是某种容器。相反,当阅读x.len()时,必须已经知道x是某种实现接口的容器,或者是从具有标准len()的类继承的容器。当没有实现映射的类有get()或key()方法,或者不是文件的类有write()方法时,我们偶尔会感到困惑。 —https://mail.python.org/pipermail/python-3000/2006-November/004643.html
为什么 join()是一个字符串方法而不是列表或元组方法?
从Python 1.6开始,字符串变得更像其他标准类型,当添加方法时,这些方法提供的功能与始终使用String模块的函数时提供的功能相同。这些新方法中的大多数已被广泛接受,但似乎让一些程序员感到不舒服的一种方法是:
“, “.join([‘1’, ‘2’, ‘4’, ‘8’, ‘16’])
结果如下:
“1, 2, 4, 8, 16”
反对这种用法有两个常见的论点。
第一条是这样的:“使用字符串文本(String Constant)的方法看起来真的很难看”,答案是也许吧,但是字符串文本只是一个固定值。如果在绑定到字符串的名称上允许使用这些方法,则没有逻辑上的理由使其在文字上不可用。
第二个异议通常是这样的:“我实际上是在告诉序列使用字符串常量将其成员连接在一起”。遗憾的是并非如此。出于某种原因,把 split() 作为一个字符串方法似乎要容易得多,因为在这种情况下,很容易看到:
“1, 2, 4, 8, 16”.split(“, “)
是对字符串文本的指令,用于返回由给定分隔符分隔的子字符串(或在默认情况下,返回任意空格)。join() 是字符串方法,因为在使用该方法时,您告诉分隔符字符串去迭代一个字符串序列,并在相邻元素之间插入自身。此方法的参数可以是任何遵循序列规则的对象,包括您自己定义的任何新的类。对于字节和字节数组对象也有类似的方法。
异常有多快?
如果没有引发异常,则try/except块的效率极高。实际上捕获异常是昂贵的。在2.0之前的Python版本中,通常使用这个习惯用法:
try:
value = mydict[key]
except KeyError:
mydict[key] = getvalue(key)
value = mydict[key]
只有当你期望dict在任何时候都有key时,这才有意义。如果不是这样的话,你就是应该这样编码:
if key in mydict:
value = mydict[key]
else:
value = mydict[key] = getvalue(key)
对于这种特定的情况,您还可以使用 value = dict.setdefault(key, getvalue(key)),但前提是调用 getvalue() 足够便宜,因为在所有情况下都会对其进行评估。
为什么Python中没有switch或case语句?
你可以通过一系列 if... elif... elif... else.轻松完成这项工作。对于switch语句语法已经有了一些建议,但尚未就是否以及如何进行范围测试达成共识。有关完整的详细信息和当前状态,请参阅 PEP 275 。
对于需要从大量可能性中进行选择的情况,可以创建一个字典,将case 值映射到要调用的函数。例如:
def function_1(…):
…
functions = {‘a’: function_1,
‘b’: function_2,
‘c’: self.method_1, …}
func = functions[value]
func()
对于对象调用方法,可以通过使用 getattr() 内置检索具有特定名称的方法来进一步简化:
def visit_a(self, …):
…
…
def dispatch(self, value):
methodname = ‘visit‘ + str(value)
method = getattr(self, methodname)
method()
建议对方法名使用前缀,例如本例中的 `visit` 。如果没有这样的前缀,如果值来自不受信任的源,攻击者将能够调用对象上的任何方法。
难道不能在解释器中模拟线程,而非得依赖特定于操作系统的线程实现吗?
答案1: 不幸的是,解释器为每个Python堆栈帧推送至少一个C堆栈帧。此外,扩展可以随时回调Python。因此,一个完整的线程实现需要对C的线程支持。
答案2: 幸运的是, Stackless Python 有一个完全重新设计的解释器循环,可以避免C堆栈。
为什么lambda表达式不能包含语句?
Python的 lambda表达式不能包含语句,因为Python的语法框架不能处理嵌套在表达式内部的语句。然而,在Python中,这并不是一个严重的问题。与其他语言中添加功能的lambda表单不同,Python的 lambdas只是一种速记符号,如果您懒得定义函数的话。
函数已经是Python中的第一类对象,可以在本地范围内声明。 因此,使用lambda而不是本地定义的函数的唯一优点是你不需要为函数创建一个名称 — 这只是一个分配了函数对象(与lambda表达式生成的对象类型完全相同)的局部变量!
可以将Python编译为机器代码,C或其他语言吗?
Cython 将带有可选注释的Python修改版本编译到C扩展中。 Nuitka 是一个将Python编译成 C++ 代码的新兴编译器,旨在支持完整的Python语言。要编译成Java,可以考虑 VOC 。
Python如何管理内存?
Python 内存管理的细节取决于实现。 Python 的标准实现 CPython 使用引用计数来检测不可访问的对象,并使用另一种机制来收集引用循环,定期执行循环检测算法来查找不可访问的循环并删除所涉及的对象。 gc 模块提供了执行垃圾回收、获取调试统计信息和优化收集器参数的函数。
但是,其他实现(如 Jython 或 PyPy ),)可以依赖不同的机制,如完全的垃圾回收器 。如果你的Python代码依赖于引用计数实现的行为,则这种差异可能会导致一些微妙的移植问题。
在一些Python实现中,以下代码(在CPython中工作的很好)可能会耗尽文件描述符:
for file in verylong_list_of_files:
f = open(file)
c = f.read(1)
实际上,使用CPython的引用计数和析构函数方案, 每个新赋值的 _f 都会关闭前一个文件。然而,对于传统的GC,这些文件对象只能以不同的时间间隔(可能很长的时间间隔)被收集(和关闭)。
如果要编写可用于任何python实现的代码,则应显式关闭该文件或使用 with 语句;无论内存管理方案如何,这都有效:
for file in very_long_list_of_files:
with open(file) as f:
c = f.read(1)
为什么CPython不使用更传统的垃圾回收方案?
首先,这不是C标准特性,因此不能移植。(是的,我们知道Boehm GC库。它包含了 大多数 常见平台(但不是所有平台)的汇编代码,尽管它基本上是透明的,但也不是完全透明的; 要让Python使用它,需要使用补丁。)
当Python嵌入到其他应用程序中时,传统的GC也成为一个问题。在独立的Python中,可以用GC库提供的版本替换标准的malloc()和free(),嵌入Python的应用程序可能希望用 它自己 替代malloc()和free(),而可能不需要Python的。现在,CPython可以正确地实现malloc()和free()。
CPython退出时为什么不释放所有内存?
当Python退出时,从全局命名空间或Python模块引用的对象并不总是被释放。 如果存在循环引用,则可能发生这种情况 C库分配的某些内存也是不可能释放的(例如像Purify这样的工具会抱怨这些内容)。 但是,Python在退出时清理内存并尝试销毁每个对象。
如果要强制 Python 在释放时删除某些内容,请使用 atexit 模块运行一个函数,强制删除这些内容。
为什么有单独的元组和列表数据类型?
虽然列表和元组在许多方面是相似的,但它们的使用方式通常是完全不同的。可以认为元组类似于Pascal记录或C结构;它们是相关数据的小集合,可以是不同类型的数据,可以作为一个组进行操作。例如,笛卡尔坐标适当地表示为两个或三个数字的元组。
另一方面,列表更像其他语言中的数组。它们倾向于持有不同数量的对象,所有对象都具有相同的类型,并且逐个操作。例如, os.listdir('.') 返回表示当前目录中的文件的字符串列表。如果向目录中添加了一两个文件,对此输出进行操作的函数通常不会中断。
元组是不可变的,这意味着一旦创建了元组,就不能用新值替换它的任何元素。列表是可变的,这意味着您始终可以更改列表的元素。只有不变元素可以用作字典的key,因此只能将元组和非列表用作key。
列表是如何在CPython中实现的?
CPython的列表实际上是可变长度的数组,而不是lisp风格的链表。该实现使用对其他对象的引用的连续数组,并在列表头结构中保留指向该数组和数组长度的指针。
这使得索引列表 a[i] 的操作成本与列表的大小或索引的值无关。
当添加或插入项时,将调整引用数组的大小。并采用了一些巧妙的方法来提高重复添加项的性能; 当数组必须增长时,会分配一些额外的空间,以便在接下来的几次中不需要实际调整大小。
字典是如何在CPython中实现的?
CPython的字典实现为可调整大小的哈希表。与B-树相比,这在大多数情况下为查找(目前最常见的操作)提供了更好的性能,并且实现更简单。
字典的工作方式是使用 hash() 内置函数计算字典中存储的每个键的hash代码。hash代码根据键和每个进程的种子而变化很大;例如,”Python” 的hash值为-539294296,而”python”(一个按位不同的字符串)的hash值为1142331976。然后,hash代码用于计算内部数组中将存储该值的位置。假设您存储的键都具有不同的hash值,这意味着字典需要恒定的时间 — O(1),用Big-O表示法 — 来检索一个键。
为什么字典key必须是不可变的?
字典的哈希表实现使用从键值计算的哈希值来查找键。如果键是可变对象,则其值可能会发生变化,因此其哈希值也会发生变化。但是,由于无论谁更改键对象都无法判断它是否被用作字典键值,因此无法在字典中修改条目。然后,当你尝试在字典中查找相同的对象时,将无法找到它,因为其哈希值不同。如果你尝试查找旧值,也不会找到它,因为在该哈希表中找到的对象的值会有所不同。
如果你想要一个用列表索引的字典,只需先将列表转换为元组;用函数 tuple(L) 创建一个元组,其条目与列表 L 相同。 元组是不可变的,因此可以用作字典键。
已经提出的一些不可接受的解决方案:
- 哈希按其地址(对象ID)列出。这不起作用,因为如果你构造一个具有相同值的新列表,它将无法找到;例如:
mydict = {[1, 2]: ‘12’}
print(mydict[[1, 2]])
- 会引发一个
KeyError异常,因为第二行中使用的[1, 2]的 id 与第一行中的 id 不同。换句话说,应该使用==来比较字典键,而不是使用is。 - 使用列表作为键时进行复制。这没有用的,因为作为可变对象的列表可以包含对自身的引用,然后复制代码将进入无限循环。
- 允许列表作为键,但告诉用户不要修改它们。当你意外忘记或修改列表时,这将产生程序中的一类难以跟踪的错误。它还使一个重要的字典不变量无效:
d.keys()中的每个值都可用作字典的键。 - 将列表用作字典键后,应标记为其只读。问题是,它不仅仅是可以改变其值的顶级对象;你可以使用包含列表作为键的元组。将任何内容作为键关联到字典中都需要将从那里可到达的所有对象标记为只读 —— 并且自引用对象可能会导致无限循环。
如果需要,可以使用以下方法来解决这个问题,但使用它需要你自担风险:你可以将一个可变结构包装在一个类实例中,该实例同时具有 __eq__() 和 __hash__() 方法。然后,你必须确保驻留在字典(或其他基于 hash 的结构)中的所有此类包装器对象的哈希值在对象位于字典(或其他结构)中时保持固定。:
class ListWrapper:
def init(self, the_list):
self.the_list = the_list
**def** __eq__(self, other):<br /> **return** self.the_list == other.the_list**def** __hash__(self):<br /> l = self.the_list<br /> result = 98767 - len(l)*555<br /> **for** i, el **in** enumerate(l):<br /> **try**:<br /> result = result + (hash(el) % 9999999) * 1001 + i<br /> **except** Exception:<br /> result = (result % 7777777) + i * 333<br /> **return** result<br />注意,哈希计算由于列表的某些成员可能不可用以及算术溢出的可能性而变得复杂。<br />此外,必须始终如此,如果 `o1 == o2` (即 `o1.__eq__(o2) is True` )则 `hash(o1) == hash(o2)``(即 ``o1.__hash__() == o2.__hash__()` ),无论对象是否在字典中。 如果你不能满足这些限制,字典和其他基于 hash 的结构将会出错。<br />对于 ListWrapper ,只要包装器对象在字典中,包装列表就不能更改以避免异常。除非你准备好认真考虑需求以及不正确地满足这些需求的后果,否则不要这样做。请留意。
为什么 list.sort() 没有返回排序列表?
在性能很重要的情况下,仅仅为了排序而复制一份列表将是一种浪费。因此, list.sort() 对列表进行了适当的排序。为了提醒您这一事实,它不会返回已排序的列表。这样,当您需要排序的副本,但也需要保留未排序的版本时,就不会意外地覆盖列表。
如果要返回新列表,请使用内置 sorted() 函数。此函数从提供的可迭代列表中创建新列表,对其进行排序并返回。例如,下面是如何迭代遍历字典并按keys排序:
for key in sorted(mydict):
… # do whatever with mydict[key]…
如何在Python中指定和实施接口规范?
由C++和Java等语言提供的模块接口规范描述了模块的方法和函数的原型。许多人认为接口规范的编译时强制执行有助于构建大型程序。
Python 2.6添加了一个 abc 模块,允许定义抽象基类 (ABCs)。然后可以使用 isinstance() 和 issubclass() 来检查实例或类是否实现了特定的ABC。 collections.abc 模块定义了一组有用的ABCs 例如 Iterable , Container , 和 MutableMapping
对于 Python,接口规范的许多好处可以通过组件的适当测试规程来获得。
一个好的模块测试套件既可以提供回归测试,也可以作为模块接口规范和一组示例。许多Python模块可以作为脚本运行,以提供简单的“自我测试”。即使是使用复杂外部接口的模块,也常常可以使用外部接口的简单“桩代码(stub)”模拟进行隔离测试。可以使用 doctest 和 unittest 模块或第三方测试框架来构造详尽的测试套件,以运行模块中的每一行代码。
适当的测试规程可以帮助在Python中构建大型的、复杂的应用程序以及接口规范。事实上,它可能会更好,因为接口规范不能测试程序的某些属性。例如, append() 方法将向一些内部列表的末尾添加新元素;接口规范不能测试您的 append() 实现是否能够正确执行此操作,但是在测试套件中检查这个属性是很简单的。
编写测试套件非常有用,您可能希望设计代码时着眼于使其易于测试。一种日益流行的技术是面向测试的开发,它要求在编写任何实际代码之前,首先编写测试套件的各个部分。当然,Python允许您草率行事,根本不编写测试用例。
为什么没有goto?
可以使用异常捕获来提供 “goto结构” ,甚至可以跨函数调用工作的 。许多人认为异常捕获可以方便地模拟C,Fortran和其他语言的 “go” 或 “goto” 结构的所有合理用法。例如:
class label(Exception): pass # declare a label
try:
…
if condition: raise label() # goto label
…
except label: # where to goto
pass
…
但是不允许你跳到循环的中间,这通常被认为是滥用goto。谨慎使用。
为什么原始字符串(r-strings)不能以反斜杠结尾?
更准确地说,它们不能以奇数个反斜杠结束:结尾处的不成对反斜杠会转义结束引号字符,留下未结束的字符串。
原始字符串的设计是为了方便想要执行自己的反斜杠转义处理的处理器(主要是正则表达式引擎)创建输入。此类处理器将不匹配的尾随反斜杠视为错误,因此原始字符串不允许这样做。反过来,允许通过使用引号字符转义反斜杠转义字符串。当r-string用于它们的预期目的时,这些规则工作的很好。
如果您正在尝试构建Windows路径名,请注意所有Windows系统调用都使用正斜杠:
f = open(“/mydir/file.txt”) # works fine!
如果您正在尝试为DOS命令构建路径名,请尝试以下示例
dir = r”\this\is\my\dos\dir” “\“
dir = r”\this\is\my\dos\dir\ “[:-1]
dir = “\this\is\my\dos\dir\“
为什么Python没有属性赋值的“with”语句?
Python有一个 ‘with’ 语句,它封装了块的执行,在块的入口和出口调用代码。有些语言的结构是这样的:
with obj:
a = 1 # equivalent to obj.a = 1
total = total + 1 # obj.total = obj.total + 1
在Python中,这样的结构是不明确的。
其他语言,如ObjectPascal、Delphi和C++ 使用静态类型,因此可以毫不含糊地知道分配给什么成员。这是静态类型的要点 — 编译器 总是 在编译时知道每个变量的作用域。
Python使用动态类型。事先不可能知道在运行时引用哪个属性。可以动态地在对象中添加或删除成员属性。这使得无法通过简单的阅读就知道引用的是什么属性:局部属性、全局属性还是成员属性?
例如,采用以下不完整的代码段:
def foo(a):
with a:
print(x)
该代码段假设 “a” 必须有一个名为 “x” 的成员属性。然而,Python中并没有告诉解释器这一点。假设 “a” 是整数,会发生什么?如果有一个名为 “x” 的全局变量,它是否会在with块中使用?如您所见,Python的动态特性使得这样的选择更加困难。
然而,Python 可以通过赋值轻松实现 “with” 和类似语言特性(减少代码量)的主要好处。代替:
function(args).mydict[index][index].a = 21
function(args).mydict[index][index].b = 42
function(args).mydict[index][index].c = 63
写成这样:
ref = function(args).mydict[index][index]
ref.a = 21
ref.b = 42
ref.c = 63
这也具有提高执行速度的副作用,因为Python在运行时解析名称绑定,而第二个版本只需要执行一次解析。
为什么 if/while/def/class语句需要冒号?
冒号主要用于增强可读性(ABC语言实验的结果之一)。考虑一下这个:
if a == b
print(a)
与
if a == b:
print(a)
注意第二种方法稍微容易一些。请进一步注意,在这个FAQ解答的示例中,冒号是如何设置的;这是英语中的标准用法。
另一个次要原因是冒号使带有语法突出显示的编辑器更容易工作;他们可以寻找冒号来决定何时需要增加缩进,而不必对程序文本进行更精细的解析。
为什么Python在列表和元组的末尾允许使用逗号?¶
Python 允许您在列表,元组和字典的末尾添加一个尾随逗号:
[1, 2, 3,]
(‘a’, ‘b’, ‘c’,)
d = {
“A”: [1, 5],
“B”: [6, 7], # last trailing comma is optional but good style
}
有几个理由允许这样做。
如果列表,元组或字典的字面值分布在多行中,则更容易添加更多元素,因为不必记住在上一行中添加逗号。这些行也可以重新排序,而不会产生语法错误。
不小心省略逗号会导致难以诊断的错误。例如:
x = [
“fee”,
“fie”
“foo”,
“fum”
]
这个列表看起来有四个元素,但实际上包含三个 : “fee”, “fiefoo” 和 “fum” 。总是加上逗号可以避免这个错误的来源。
允许尾随逗号也可以使编程代码更容易生成。
核心语言
当变量有值时,为什么会出现UnboundLocalError?
通过在函数体中的某处添加赋值语句,导致以前正常工作的代码被修改而得到 UnboundLocalError 会令人感到意外。
以下代码:
>>>
>>> x = 10
>>> def bar():
… print(x)
>>> bar()
10
正常工作,但是以下代码
>>>
>>> x = 10
>>> def foo():
… print(x)
… x += 1
会得到一个 UnboundLocalError :
>>>
>>> foo()
Traceback (most recent call last):
…
UnboundLocalError: local variable ‘x’ referenced before assignment
这是因为当你对作用域中的变量进行赋值时,该变量将成为该作用域的局部变量,并在外部作用域中隐藏任何类似命名的变量。由于foo中的最后一个语句为 x 分配了一个新值,编译器会将其识别为局部变量。因此,当先前的 print(x) 尝试打印未初始化的局部变量时会导致错误。
在上面的示例中,你可以通过将其声明为全局来访问外部作用域变量:
>>>
>>> x = 10
>>> def foobar():
… global x
… print(x)
… x += 1
>>> foobar()
10
这个显式声明是必需的,以便提醒你(与类和实例变量的表面类似情况不同),你实际上是在外部作用域中修改变量的值
>>>
>>> print(x)
11
你可以使用 nonlocal 关键字在嵌套作用域中执行类似的操作:
>>> def foo():... x = 10... def bar():... nonlocal x... print(x)... x += 1... bar()... print(x)>>> foo()1011
Python中的局部变量和全局变量有哪些规则?
在Python中,仅在函数内引用的变量是隐式全局变量。如果在函数体内的任何位置为变量赋值,则除非明确声明为全局,否则将其视为局部值。
虽然起初有点令人惊讶,但片刻考虑就可以解释。一方面,要求 global 表示已分配的变量可以防止意外的副作用。另一方面,如果所有全局引用都需要 global ,那么你一直都在使用 global 。你必须将对内置函数或导入模块的组件的每个引用声明为全局。这种杂乱会破坏 global 声明用于识别副作用的有用性。
为什么在具有不同值的循环中定义的lambdas都返回相同的结果?
假设你使用for循环来定义几个不同的 lambda (甚至是普通函数),例如::
>>>
>>> squares = []
>>> for x in range(5):
… squares.append(lambda: x2)
这给你一个包含5个lambdas的列表,它们计算 `x2。你可能会期望,当它们被调用时,它们将分别返回0、1、4、9和16。但是,当你真正尝试时,你会看到它们都返回16。:<br />>>><br />**>>> **squares[2]()<br />16<br />**>>> **squares[4]()<br />16<br />发生这种情况是因为x不是lambdas的内部变量,而是在外部作用域中定义,并且在调用lambda时访问它 - 而不是在定义它时。 在循环结束时,x的值是4,所以所有的函数现在返回42,即16。你还可以通过更改x` 的值来验证这一点,并查看lambdas的结果如何变化:
>>>
>>> x = 8
>>> squares2
64
为了避免这种情况,你需要将值保存在lambdas的局部变量中,这样它们就不依赖于全局x 的值
>>>
>>> squares = []
>>> for x in range(5):
… squares.append(lambda n=x: n2)
这里, n=x 在lambda本地创建一个新的变量 n ,并在定义lambda时计算,使它具有与 x 在循环中该点相同的值。这意味着 n 的值在第一个lambda中为 0 ,在第二个lambda中为 1 ,在第三个中为 2 ,依此类推。因此每个lambda现在将返回正确的结果:
>>>
>>> squares2
4
>>> squares4
16
请注意,这种行为并不是lambda所特有的,但也适用于常规函数。
如何跨模块共享全局变量?
在单个程序中跨模块共享信息的规范方法是创建一个特殊模块(通常称为config或cfg)。只需在应用程序的所有模块中导入配置模块;然后该模块可用作全局名称。因为每个模块只有一个实例,所以对模块对象所做的任何更改都会在任何地方反映出来。 例如:
config.py:
x = 0 # Default value of the 'x' configuration setting
mod.py:
import config
config.x = 1
main.py:
import config
import mod
print(config.x)
请注意,出于同样的原因,使用模块也是实现Singleton设计模式的基础。
导入模块的“最佳实践”是什么?
通常,不要使用 from modulename import * 。这样做会使导入器的命名空间变得混乱,并且使得连接器更难以检测未定义的名称。
在文件的顶部导入模块。这样做可以清楚地了解代码所需的其他模块,并避免了模块名称是否在范围内的问题。每行导入一个模块可以轻松添加和删除导入的模块,但每行导入多个模块会占用更少的屏幕空间。
如果按以下顺序导入模块,这是一种很好的做法:
- 标准库模块 — 例如:
sys,os,getopt,re - 第三方库模块(安装在Python的site-packages目录中的任何内容) — 例如mx.DateTime,ZODB,PIL.Image等
- 本地开发的模块
有时需要将模块导入语句移动到函数或类里面,以避免循环导入问题。Gordon McMillan 说:
当两个模块都使用 “import
“ 的导入形式时,循环导入就可以了。但是当第 2 个模块想从第 1 个模块中获取一个名称 (“from module import name”) 并且导入位于顶层时,就会出错。 这是因为第 1 个模块中的名称还不可用,因为第 1 个模块正在忙着导入第 2 个模块。
在这种情况下,如果第二个模块仅用于一个函数,则可以轻松地将模块导入语句移动到该函数中。调用导入时,第一个模块将完成初始化,第二个模块可以进行导入。
如果某些模块是特定于平台的,则可能还需要将模块导入语句移出顶级代码。在这种情况下,甚至可能无法导入文件顶部的所有模块。在这种情况下,在相应的特定于平台的代码中导入正确的模块是一个很好的选择。
只有当需要解决诸如避免循环导入或试图减少模块初始化时间的问题时,才可以将导入移动到本地范围,例如在函数定义中。如果根据程序的执行方式,许多导入是不必要的,这种技术尤其有用。如果仅在某个函数中使用模块,您还可能希望将导入移到该函数中。请注意,第一次加载模块可能会因为模块的一次初始化而代价高昂,但多次加载模块实际上是免费的,只需进行几次字典查找。即使模块名称超出了作用域,模块也可能在 sys.modules 中可用。
为什么对象之间会共享默认值?
这种类型的缺陷通常会惹恼新手程序员。考虑这个函数
def foo(mydict={}): # Danger: shared reference to one dict for all calls
… compute something …
mydict[key] = value
return mydict
第一次调用此函数时,mydict 包含一项。第二次,mydict 包含两项,因为当 foo() 开始执行时, mydict 中已经有一项了。
函数调用经常被期望为默认值创建新的对象。 但实际情况并非如此。 默认值会在函数定义时一次性地创建。 如果对象发生改变,就如本示例中的字典那样,则对函数的后续调用将会引用这个被改变的对象。
按照定义,不可变对象例如数字、字符串、元组和 None 因为不可变所以是安全的。 对可变对象例如字典、列表和类实例的改变则可能造成迷惑。
由于这一特性,在编程中应遵循的一项好习惯是不使用可变对象作为默认值。 而应使用 None 作为默认值和函数中的值,检查值为 None 的形参并创建相应的列表、字典或其他可变对象。 例如,不要这样写:
def foo(mydict={}):
…
而要这样写:
def foo(mydict=None):
if mydict is None:
mydict = {} # create a new dict for local namespace
这一特性有时会很有用处。 当你有一个需要进行大量耗时计算的函数时,一个常见技巧是将每次调用函数的参数和结果值缓存起来,并在同样的值被再次请求时返回缓存的值。 这称为“记忆”,具体实现方式可以是这样的:
# Callers can only provide two parameters and optionally pass _cache by keyword
def expensive(arg1, arg2, , _cache={}):
if (arg1, arg2) in _cache:
*return _cache[(arg1, arg2)]
_# Calculate the value_<br /> result = ... expensive computation ...<br /> _cache[(arg1, arg2)] = result _# Store result in the cache_<br /> **return** result<br />你也可以使用包含一个字典的全局变量而不使用参数默认值;这完全取决于个人偏好。
如何将可选参数或关键字参数从一个函数传递到另一个函数?
使用函数参数列表中的 * 和 ** 说明符收集参数;这会将位置参数作为元组,将关键字参数作为字典。然后,您可以使用 * 和 ** 调用另一个函数时传递这些参数:
def f(x, args, **kwargs):
…
kwargs[‘width’] = ‘14.3c’
…
g(x, args, **kwargs)
形参和实参之间有什么区别?
形参 是指出现在函数定义中的名称,而 实参 则是在调用函数时实际传入的值。 形参定义了一个函数能接受何种类型的实参。 例如,对于以下函数定义:
def func(foo, bar=None, kwargs):
pass**
foo, bar 和 kwargs 是 func 的形参。 但是,在调用 func 时,例如:
func(42, bar=314, extra=somevar)
实际的值 42, 314 和 somevar 则是实参。
为什么更改列表 ‘y’ 也会更改列表 ‘x’?
如果你编写的代码就像下面一样:
>>>
>>> x = []
>>> y = x
>>> y.append(10)
>>> y
[10]
>>> x
[10]
你可能想知道为什么追加一个元素也改变了x。
产生这种结果有两个因素:
- 变量只是指向具体对象的名称。 执行
y = x并不会为列表创建一个副本 —— 它只是创建了一个新变量y指向x所指向的同一对象。 这意味着只存在一个对象(列表),x和y都是对它的引用。 - 列表属于 mutable 对象,这意味着你可以改变它的内容。
在调用 append() 之后,这个可变对象的内容由 [] 变为 [10]。 由于两个变量都指向同一对象,因此使用任何一个名称所访问到的都是修改后的值 [10]。
如果我们改为将不可变对象赋值给 x:
>>>
>>> x = 5 # ints are immutable
>>> y = x
>>> x = x + 1 # 5 can’t be mutated, we are creating a new object here
>>> x
6
>>> y
5
我们可以看到在此情况下 x 和 y 就不再相等了。 这是因为整数是 immutable 对象,当我们执行 x = x + 1 时我们并不是改变了 5 这个对象的值;而是创建了一个新的对象 (整数 6) 并将其赋值给 x (也就是改变了 x 所指向的对象)。 在赋值之后我们就有了两个对象 (整数 6 和 5) 以及分别指向它们的两个变量 (x 现在指向 6 而 y 仍然指向 5)。
某些操作 (例如 y.append(10) 和 y.sort()) 是改变原对象,而看上去相似的另一些操作 (例如 y = y + [10] 和 sorted(y)) 则是创建新对象。 通常在 Python 中 (以及在标准库的所有代码中) 会改变原对象的方法将返回 None 以帮助避免混淆这两种不同类型的操作。 因此如果你错误地使用了 y.sort() 并期望它将返回一个经过排序的 y 的副本,你得到的结果将会是 None,这将导致你的程序产生一个容易诊断的错误。
但是,还存在一类操作,不同的类型执行相同的操作会有不同的行为:那就是增强赋值运算符。 例如,+= 会原地改变列表,但不会改变元组或整数 (a_list += [1, 2, 3] 与 a_list.extend([1, 2, 3]) 一样都会改变 a_list,而 some_tuple += (1, 2, 3) 和 some_int += 1 则会创建新的对象)。
换而言之:
- 如果我们有一个可变对象 (
list,dict,set等等),我们可以使用某些特定的操作来改变它,所有指向它的变量都会显示它的改变。 - 如果我们有一个不可变对象 (
str,int,tuple等等),所有指向它的变量都将显示相同样的值,但凡是会改变这个值的操作将总是返回一个新对象。
如果你想知道两个变量是否指向相同的对象,你可以使用 is 运算符,或内置函数 id()。
如何编写带输出参数的函数(通过引用调用)?
请记住在 Python 中参数是通过赋值来传递的。 由于赋值只是创建了对象的引用,因此在调用者和被调用者的参数名称之间没有别名,所以本身是没有按引用调用的。 你可以通过多种方式实现所需的效果。
- 通过返回一个结果元组:
>>>
>>> def func1(a, b):
… a = ‘new-value’ # a and b are local names
… b = b + 1 # assigned to new objects
… return a, b # return new values
…
>>> x, y = ‘old-value’, 99
>>> func1(x, y)
(‘new-value’, 100)
- 这几乎总是最清晰明了的解决方案。
- 通过使用全局变量。 这种方式不是线程安全的,而且也不受推荐。
- 通过传递一个可变 (即可原地修改的) 对象:
>>>
>>> def func2(a):
… a[0] = ‘new-value’ # ‘a’ references a mutable list
… a[1] = a[1] + 1 # changes a shared object
…
>>> args = [‘old-value’, 99]
>>> func2(args)
>>> args
[‘new-value’, 100]
- 通过传递一个会被改变的字典:
>>>
>>> def func3(args):
… args[‘a’] = ‘new-value’ # args is a mutable dictionary
… args[‘b’] = args[‘b’] + 1 # change it in-place
…
>>> args = {‘a’: ‘old-value’, ‘b’: 99}
>>> func3(args)
>>> args
{‘a’: ‘new-value’, ‘b’: 100}
- 或者在一个类实例中捆绑值:
>>>
>>> class Namespace:
… def init(self, /, args):
… for key, value in args.items():
… setattr(self, key, value)
…
>>> def func4(args):
… args.a = ‘new-value’ # args is a mutable Namespace
… args.b = args.b + 1 # change object in-place
…
>>> args = Namespace(a=’old-value’, b=99)
>>> func4(args)
>>> **vars(args)
{‘a’: ‘new-value’, ‘b’: 100}
- 几乎没有任何适当理由将问题如此复杂化。
如何在Python中创建高阶函数?
你有两种选择:使用嵌套作用域,或者使用可调用对象。 例如,假设你想要定义 linear(a,b) 使其返回一个函数 f(x) 来设计 a*x+b 的值。 可以使用以下嵌套作用域:
def linear(a, b):
def result(x):
return a x + b
return result
或使用一个可调用对象:
class *linear:
**def** __init__(self, a, b):<br /> self.a, self.b = a, b
**def** __call__(self, x):<br /> **return** self.a * x + self.b<br />在两种情况下,:<br />taxes = linear(0.3, 2)<br />都会给出一个可调用对象,使得 `taxes(10e6) == 0.3 * 10e6 + 2`.<br />可调用对象方式的缺点是速度略慢且生成的代码略长。 但是,请注意一组可调用对象能够通过继承来共享签名:<br />**class** **exponential**(linear):<br /> _# __init__ inherited_<br /> **def** __call__(self, x):<br /> **return** self.a * (x ** self.b)<br />对象可以封装多个方法的状态:<br />**class** **counter**:
value = 0
**def** set(self, x):<br /> self.value = x
**def** up(self):<br /> self.value = self.value + 1
**def** down(self):<br /> self.value = self.value - 1
count = counter()
inc, dec, reset = count.up, count.down, count.set
这里 inc(), dec() 和 reset() 将表现为共享同一计数变量的多个函数。
如何在Python中复制对象?
一般来说,通常情况下请尝试 copy.copy() 或 copy.deepcopy()。 不是所有对象都可以复制,但多数都是可以的。
某些对象可以方便地复制。 例如字典具有 copy() 方法:
newdict = olddict.copy()
序列可以通过切片来复制:
new_l = l[:]
如何找到对象的方法或属性?
对于一个用户自定义类的实例 x,dir(x) 将返回一个按字母顺序排序的包含实例属性和方法及其类所定义的属性名称的列表。
我的代码如何才能发现对象的名称?
通常来说是做不到的,因为对象并不真正具有名称。 在本质上,赋值总是会将一个名称绑定到某个值;def 和 class 语句也是如此,但在这种情况下该值是一个可调用对象。 考虑以下代码:
>>>
>>> class A:
… pass
…
>>> B = A
>>> a = B()
>>> b = a
>>> print(b)
<__main__.A object at 0x16D07CC>
>>> print(a)
<__main__.A object at 0x16D07CC>
不严谨地讲,该类有一个名称:虽然它是绑定了两个名称并通过名称 B 发起调用,所创建的实例仍然被视为类 A 的一个实例。 但是实例的名称则无法确定地说是 a 或是 b,因为有两个名称被绑定到了同一个值。
一般来说你的代码应该没有必要“知道”特定值的名称。 除非你是在编写特殊的内省程序,出现这样的问题通常表明如果改变方式可能会更有利。
在 comp.lang.python 中,Fredrik Lundh 在回答这样的问题时曾经给出过一个绝佳的类比:
跟你找出在你家门廊见到的某只猫的名字所用的办法一样:猫(对象)自己无法告诉你它的名字,它根本就不在乎 —— 所以找出它叫什么名字的唯一办法是问你的所有邻居(命名空间)那是不是他们的猫(对象)…… ……并且如果你发现它有很多名字或根本没有名字也不必觉得惊讶!
逗号运算符的优先级是什么?
逗号在 Python 中不是运算符。 考虑这个例子:
>>>
>>> “a” in “b”, “a”
(False, ‘a’)
由于逗号不是运算符而是表达式之间的分隔符,以上代码的含义就相当于:
(“a” in “b”), “a”
而不是:
“a” in (“b”, “a”)
对于各种赋值运算符 (=, += 等) 来说同样如此。 它们并不是真正的运算符而是赋值语句中的语法分隔符。
是否有与 C 的 “?:” 三目运算符等价的东西?
有的。 相应语法如下:
[on_true] if [expression] else [on_false]
x, y = 50, 25
small = x if x < y else y
在 Python 2.5 引入此语法之前,常见的做法是使用逻辑运算符:
[expression] and [ontrue] or [on_false]
然而这种做法并不保险,因为当 _on_true 具有布尔假值时将会给出错误的结果。 所以,使用 ... if ... else ... 形式总是会更好。
是否可以用Python编写混淆的单行程序?
可以。通常是在 lambda 中嵌套 lambda 来实现的。请参阅以下三个来自 Ulf Bartelt 的示例代码:
from functools import reduce
# Primes < 1000
print(list(filter(None,map(lambda y:yreduce(lambda x,y:xy!=0,
map(lambda x,y=y:y%x,range(2,int(pow(y,0.5)+1))),1),range(2,1000)))))
# First 10 Fibonacci numbers
print(list(map(lambda x,f=lambda x,f:(f(x-1,f)+f(x-2,f)) if x>1 else 1:
f(x,f), range(10))))
# Mandelbrot set
print((lambda Ru,Ro,Iu,Io,IM,Sx,Sy:reduce(lambda x,y:x+y,map(lambda y,
Iu=Iu,Io=Io,Ru=Ru,Ro=Ro,Sy=Sy,L=lambda yc,Iu=Iu,Io=Io,Ru=Ru,Ro=Ro,i=IM,
Sx=Sx,Sy=Sy:reduce(lambda x,y:x+y,map(lambda x,xc=Ru,yc=yc,Ru=Ru,Ro=Ro,
i=i,Sx=Sx,F=lambda xc,yc,x,y,k,f=lambda xc,yc,x,y,k,f:(k<=0)or (xx+yy
>=4.0) or 1+f(xc,yc,xx-yy+xc,2.0xy+yc,k-1,f):f(xc,yc,x,y,k,f):chr(
64+F(Ru+x(Ro-Ru)/Sx,yc,0,0,i)),range(Sx))):L(Iu+y(Io-Iu)/Sy),range(Sy
))))(-2.1, 0.7, -1.2, 1.2, 30, 80, 24))
# __ / \ _/ | | | lines on screen
# V V | |__ columns on screen
# | | |__ maximum of “iterations”
# | |_ range on y axis
# |__ range on x axis_
请不要在家里尝试,骚年!
函数参数列表中的斜杠(/)是什么意思?
函数参数列表中的斜杠表示在它之前的形参是仅限位置形参。 仅限位置形参没有外部可用的名称。 在调用接受仅限位置形参的函数时,参数只会基于它们的位置被映射到形参。 例如,divmod() 是一个接受仅限位置形参的函数。 它的文档是这样的:
>>>
>>> help(divmod)
Help on built-in function divmod in module builtins:
divmod(x, y, /)
Return the tuple (x//y, x%y). Invariant: divy + mod == x.
在形参列表末尾的斜杠意味着两个形参都是仅限位置形参。 因此,附带关键字参数调用 divmod() 将会导致报错:
>>>
*>>> divmod(x=3, y=4)
Traceback (most recent call last):
File “
TypeError: divmod() takes no keyword arguments
数字和字符串
如何指定十六进制和八进制整数?
要指定一个八进制数码,则在八进制值之前加一个零和一个小写或大写字母 “o” 作为前缀。 例如,要将变量 “a” 设为八进制的 “10” (十进制的 8),就输入:
>>>
>>> a = 0o10
>>> a
8
十六进制数也同样简单。 只要在十六进制数之前加一个零和一个小写或大写字母 “x”。 十六进制数码中的字母可以为大写或小写。 例如在 Python 解释器中输入:
>>>
>>> a = 0xa5
>>> a
165
>>> b = 0XB2
>>> b
178
为什么-22 // 10返回-3?
这主要是为了让 i % j 的正负与 j 一致,如果你想要这样的结果,并且又想要:
i == (i // j) * j + (i % j)
那么整除就必须向下取整。 C 同样要求保持一致,并且编译器在截短 i // j 的结果值时需要使 i % j 的正负与 i 一致。
对于 i % j 来说 j 为负值的应用场景实际上是非常少的。 而 j 为正值的情况则非常多,并且实际上在所有情况下让 i % j 的结果为 >= 0 会更有用处。 如果现在时间为 10 时,那么 200 小时前应是几时? -190 % 12 == 2 是有用处的;-190 % 12 == -10 则是会导致意外的漏洞。
如何将字符串转换为数字?
对于整数,可使用内置的 int() 类型构造器,例如 int('144') == 144。 类似地,可使用 float() 转换为浮点数,例如 float('144') == 144.0。
默认情况下,这些操作会将数字按十进制来解读,因此 int('0144') == 144 而 int('0x144') 会引发 ValueError。 int(string, base) 接受第二个可选参数指定转换的基数,例如 int('0x144', 16) == 324。 如果指定基数为 0,则按 Python 规则解读数字:前缀 ‘0o’ 表示八进制,而 ‘0x’ 表示十六进制。
如果你只是想将字符串转为数字,请不要使用内置函数 eval()。 eval() 的速度会慢很多并且有安全风险:别人可能会传入具有你不想要的附带效果的 Python 表达式。 例如,别人可以传入 __import__('os').system("rm -rf $HOME") 这将删除你的家目录。eval() 还具有将数字解读为 Python 表达式的效果,这样 eval('09') 将会导致语法错误,因为 Python 不允许十进制数的首位是 ‘0’ (‘0’ 除外)。
如何将数字转换为字符串?
例如要将数字 144 转换为字符串 ‘144’,可使用内置类型构造器 str()。 如果想要表示为十六进制或八进制数,可使用内置函数 hex() 或 oct()。 想要更好地格式化,请参阅 格式化字符串字面值 和 格式字符串语法 等小节,例如 "{:04d}".format(144) 生成 '0144' 而 "{:.3f}".format(1.0/3.0) 生成 '0.333'。
如何修改字符串?
无法修改,因为字符串是不可变对象。 在大多数情况下,你应该使用你想要的各种部分来构造一个新字符串。 但是,如果你想要一个可以原地修改 Unicode 数据的对象,可尝试使用 io.StringIO 对象或 array 模块:
>>>
>>> import io
>>> s = “Hello, world”
>>> sio = io.StringIO(s)
>>> sio.getvalue()
‘Hello, world’
>>> sio.seek(7)
7
>>> sio.write(“there!”)
6
>>> sio.getvalue()
‘Hello, there!’
>>> import array
>>> a = array.array(‘u’, s)
>>> print(a)
array(‘u’, ‘Hello, world’)
>>> a[0] = ‘y’
>>> print(a)
array(‘u’, ‘yello, world’)
>>> a.tounicode()
‘yello, world’
如何使用字符串调用函数/方法?
有多种技巧可供选择。
- 最好的做法是使用一个将字符串映射到函数的字典。 这一技巧的主要优势在于字符串不必与函数名称一致。 这也是用于模拟其他语言中 case 结构的主要技巧:
def a():
pass
def b():
pass
dispatch = {‘go’: a, ‘stop’: b} # Note lack of parens for funcs
dispatchget_input() # Note trailing parens to call function
- 使用内置函数
getattr()
import foo
getattr(foo, ‘bar’)()
- 请注意
getattr()可用于任何对象,包括类、类实例、模块等等。
在标准库中多次使用了这个技巧,例如:
class Foo:
def do_foo(self):
…
**def** do_bar(self):<br /> ...
f = getattr(fooinstance, ‘do‘ + opname)
f()
def myFunc():
print(“hello”)
fname = “myFunc”
f = locals()[fname]
f()
f = eval(fname)
f()
注意:使用
eval()速度慢而且危险。 如果你不能绝对掌控字符串的内容,别人将能传入可被解析为任意函数直接执行的字符串。是否有与Perl 的chomp() 等效的方法,用于从字符串中删除尾随换行符?
可以使用
S.rstrip("\r\n")从字符串S的末尾删除所有的换行符,而不删除其他尾随空格。如果字符串S表示多行,且末尾有几个空行,则将删除所有空行的换行符:
>>>
>>> lines = (“line 1 \r\n“
… “\r\n“
… “\r\n“)
>>> lines.rstrip(“\n\r“)
‘line 1 ‘
由于通常只在一次读取一行文本时才需要这样做,所以使用S.rstrip()这种方式工作得很好。是否有 scanf() 或 sscanf() 的对应物?
没有这样的对应物。
对于简单的输入解析,最方便的做法通常是使用字符串对象的split()方法将一行内容拆解为以空格分隔的单词,然后使用int()或float()将表示十进制数的字符串转换为数值。split()支持可选的 “sep” 形参,适用于内容行使用空格符以外的分隔符的情况。
以于更复杂的输入解析,正则表达式会比 C 的sscanf()更强大,也更适合此类任务。‘UnicodeDecodeError’ 或 ‘UnicodeEncodeError’ 错误是什么意思?
性能
我的程序太慢了。该如何加快速度?
总的来说,这是个棘手的问题。首先,下面列出了深入了解前需要记住的事情:
不同的 Python 实现具有不同的性能特点。 本 FAQ 着重解答的是 CPython。
- 行为可能因操作系统而异,尤其是在谈论 I / O 或多线程时。
- 在尝试优化任何代码 前 ,应始终找到程序中的热点(请参阅
profile模块)。 - 编写基准脚本将允许您在搜索改进时快速迭代(请参阅
timeit模块)。 - 强烈建议在可能引入隐藏在复杂优化中的回归之前,要有良好的代码覆盖率(通过单元测试或任何其他技术)。
话虽如此,加速Python代码有很多技巧。以下是一些可以达到可接受的性能水平的一般原则:
- 使您的算法更快(或更改为更快的算法)可以产生比尝试在代码中使用微优化技巧更大的好处。
- 使用正确的数据结构。参考文档 内置类型 和
collections模块。 - 当标准库提供用于执行某些操作的原语时,可能(尽管不能保证)比您可能提出的任何替代方案更快。对于用C编写的原语,例如内置函数和一些扩展类型,这是真的。例如,请确保使用
list.sort()内置方法或相关的sorted()函数进行排序(有关适度高级用法的示例,请参阅 排序指南 )。 - 抽象倾向于创造间接性并迫使翻译更多地工作。如果间接级别超过完成的有用工作量,则程序将变慢。你应该避免过度抽象,特别是在微小的功能或方法的形式下(这通常也会对可读性产生不利影响)。
如果你已经达到纯 Python 允许的限制,那么有一些工具可以让你走得更远。 例如, Cython 可以将稍微修改的 Python 代码版本编译为 C 扩展,并且可以在许多不同的平台上使用。 Cython 可以利用编译(和可选的类型注释)来使代码明显快于解释运行时的速度。 如果您对 C 编程技能有信心,也可以自己 编写 C 扩展模块 。
参见
专门介绍 性能提示 的wiki页面。
将多个字符串连接在一起的最有效方法是什么?
str 和 bytes 对象是不可变的,因此将多个字符串连接在一起效率很低,因为每个连接都会创建一个新对象。在一般情况下,总运行时间是总字符串长度的二次方。
要连接多个 str 对象,通常推荐的用法是将它们放入一个列表中并在结尾处调用 str.join() :
chunks = []
for s in my_strings:
chunks.append(s)
result = ‘’.join(chunks)
(另一个合理有效的惯用方法是 io.StringIO )
要连接多个 str 对象,建议使用本地连接( += 运算符)扩展 bytearray 对象:
result = bytearray()
for b in my_bytes_objects:
result += b
序列(元组/列表)
如何在元组和列表之间进行转换?
类型构造器 tuple(seq) 可将任意序列(实际上是任意可迭代对象)转换为具有相同排列顺序的相同条目的元组。
例如,tuple([1, 2, 3]) 产生 (1, 2, 3) 而 tuple('abc') 产生 ('a', 'b', 'c')。 如果参数为一个元组,它不会创建副本而是返回同一对象,因此如果你不确定某个对象是否为元组时也可简单地调用 tuple()。
类型构造器 list(seq) 可将任意序列或可迭代对象转换为具有相同排列顺序的相同条目的列表。 例如,list((1, 2, 3)) 产生 [1, 2, 3] 而 list('abc') 产生 ['a', 'b', 'c']。 如果参数为一个列表,它会像 seq[:] 那样创建一个副本。
什么是负数序号?
Python 序列使用正数或负数作为序号或称索引号。 对于正数序号,第一个序号为 0 而 1 为第二个序号,依此类推。 对于负数序号,倒数第一个序号为 -1 而倒数第二个序号为 -2,依此类推。 可以认为 seq[-n] 就相当于 seq[len(seq)-n]。
使用负数序号有时会很方便。 例如 S[:-1] 就是原字符串去掉最后一个字符,这可以用来移除某个字符串末尾的换行符。
如何以相反的顺序迭代序列?
使用 reversed() 内置函数,这是Python 2.4中的新功能:
for x in reversed(sequence):
… # do something with x …
这不会修改您的原始序列,而是构建一个反向顺序的新副本以进行迭代。
在 Python 2.3 里,您可以使用扩展切片语法:
for x in sequence[::-1]:
… # do something with x …
如何从列表中删除重复项?
有关执行此操作的许多方法的详细讨论,请参阅 Python Cookbook:
如果您不介意重新排序列表,请对其进行排序,然后从列表末尾进行扫描,删除重复项:
if mylist:
mylist.sort()
last = mylist[-1]
for i in range(len(mylist)-2, -1, -1):
if last == mylist[i]:
del mylist[i]
else:
last = mylist[i]
如果列表的所有元素都可以用作设置键(即:它们都是 hashable ),这通常会更快:
mylist = list(set(mylist))
这会将列表转换为集合,从而删除重复项,然后返回到列表中。
如何从列表中删除多个项?
对于删除重复项,一种可能的做法是设置删除条件显式地进行反向迭代。但是更容易也更快速的做法是进行隐式或显式的正向迭代并使用切片替代。 以下列出了三种方式。:
mylist[:] = filter(keep_function, mylist)
mylist[:] = (x for x in mylist if keep_condition)
mylist[:] = [x for x in mylist if keep_condition]
如果空格没有关系,则列表推导式将是最快速的。
如何在Python中创建数组?
使用列表:
[“this”, 1, “is”, “an”, “array”]
列表在时间复杂度方面相当于C或Pascal数组;主要区别在于,python列表可以包含许多不同类型的对象。array 模块还提供了创建具有紧凑表示的固定类型的数组的方法,但它的索引速度比列表慢。还要注意,数字扩展和其他扩展还定义了具有各种特性的类似数组的结构。
要获取Lisp样式的列表,可以使用元组模拟cons单元:
lisp_list = (“like”, (“this”, (“example”, None) ) )
如果需要可变性,可以使用列表而不是元组。这里模拟lisp car的是 lisp_list[0] ,模拟cdr的是 lisp_list[1] 。只有在你确定真的需要的时候才这样做,因为它通常比使用Python列表慢得多。
如何创建多维列表?
你可能试图制作一个像这样的多维数组:
>>>
>>> A = [[None] 2] 3
如果你打印它,看起来是正确的:
>>> A
[[None, None], [None, None], [None, None]]
但是,当你给某一项赋值时,会同时在多个位置显示变化:
>>> A[0][0] = 5
>>> A
[[5, None], [5, None], [5, None]]
其中的原因在于使用 * 对列表执行重复操作并不是创建副本,它只是创建现有对象的引用。 *3 创建了对长度为二的同一列表的 3 个引用。 对某一行的改变会作用于所有行,通常这一定不是你所希望的。
建议的做法是先创建一个所需长度的列表,然后其中的元素再以一个新创建的列表来填充:
A = [None] 3
for i in range(3):
A[i] = [None] 2
这样就生成了一个包含 3 个长度为二的不同列表的列表。 你也可以使用列表推导式:
w, h = 2, 3
A = [[None] w for i *in range(h)]
或者你还可以使用提供矩阵类型的扩展包;其中最著名的是 NumPy。
如何将方法应用于一系列对象?
可以使用列表推导式:
result = [obj.method() for obj in mylist]
为什么 a_tuple[i] += [‘item’] 会在执行加法时引发异常?
这是由两个事实共同导致的结果,一是增强赋值运算符属于 赋值 运算符,二是在 Python 中存在可变和不可变两种不同的对象。
此处的讨论在任何对元组中指向可变对象的元素使用增强赋值运算符的情况都是普遍成立的,但在此我们只以 list 和 += 来举例。
如果你写成这样:
>>>
>>> atuple = (1, 2)
>>> a_tuple[0] += 1
Traceback (most recent call last):
…
TypeError: ‘tuple’ object does not support item assignment
发生异常的原因是显而易见的: 1 会与对象 a_tuple[0] 相加,而该对象为 (1),得到结果对象 2,但当我们试图将运算结果 2 赋值给元组的 0 号元素时就将报错,因为我们不能改变元组的元素所指向的对象。
在表层之处,以上增强赋值语句所做的大致是这样:
>>>
>>> result = a_tuple[0] + 1
>>> a_tuple[0] = result
Traceback (most recent call last):
…
TypeError: ‘tuple’ object does not support item assignment
由于元组是不可变的,因此操作的赋值部分会引发错误。
当你这样写的时候:
>>>
>>> a_tuple = ([‘foo’], ‘bar’)
>>> a_tuple[0] += [‘item’]
Traceback (most recent call last):
…
TypeError: ‘tuple’ object does not support item assignment
发生异常会令人略感吃惊,还有一个更为令人吃惊的事实:虽然有报错,但是添加操作却生效了:
>>>
>>> atuple[0]
[‘foo’, ‘item’]
要明白为何会这样,你需要知道 (a) 如果一个对象实现了 __iadd__ 魔术方法,它会在执行 += 增强赋值时被调用,并且其返回值将用于该赋值语句; (b) 对于列表来说,__iadd__ 等价于在列表上调用 extend 并返回该列表。 因此对于列表我们可以说 += 就是 list.extend 的“快捷方式”:
>>>
>>> alist = []
>>> alist += [1]
>>> alist
[1]
这相当于:
>>>
>>> result = a_list.__iadd([1])
>>> a_list = result
a_list 所引用的对象已被修改,而引用被修改对象的指针又重新被赋值给 a_list。 赋值的最终结果没有变化,因为它是引用 a_list 之前所引用的同一对象的指针,但仍然发生了赋值操作。
因此,在我们的元组示例中,发生的事情等同于:
>>>
>>> result = a_tuple[0].__iadd([‘item’])
>>> a_tuple[0] = result
Traceback (most recent call last):
…
TypeError: ‘tuple’ object does not support item assignment
`_iadd成功执行,因此列表得到了扩充,但是虽然result指向了a_tuple[0]` 已经指向的同一对象,最后的赋值仍然导致了报错,因为元组是不可变的。
我想做一个复杂的排序:你能用Python做一个Schwartzian变换吗?
该技术归功于Perl社区的 Randal Schwartz,它通过将每个元素映射到其 “排序值(sort value)” 的度量对列表中的元素进行排序。在Python中,使用 list.sort() 方法的 key 参数:
Isorted = L[:]
Isorted.sort(key=lambda s: int(s[10:15]))
如何按其他列表中的值对一个列表进行排序?
将它们合并到元组的迭代器中,对结果列表进行排序,然后选择所需的元素。
>>>
>>> list1 = [“what”, “I’m”, “sorting”, “by”]
>>> list2 = [“something”, “else”, “to”, “sort”]
>>> pairs = zip(list1, list2)
>>> pairs = sorted(pairs)
>>> pairs
[(“I’m”, ‘else’), (‘by’, ‘sort’), (‘sorting’, ‘to’), (‘what’, ‘something’)]
>>> result = [x[1] for x in pairs]
>>> result
[‘else’, ‘sort’, ‘to’, ‘something’]
最后一步的替代方案是:
>>>
>>> result = []
>>> for p in pairs: result.append(p[1])
如果你觉得这个更容易读懂,那么你可能更喜欢使用这个而不是前面的列表推导。然而,对于长列表来说,它的速度几乎是原来的两倍。为什么?首先, append() 操作必须重新分配内存,虽然它使用了一些技巧来避免每次都这样做,但它仍然偶尔需要这样做,而且代价相当高。第二,表达式 “result.append” 需要额外的属性查找。第三,必须执行所有这些函数调用会降低速度。
对象
什么是类?
类 是通过执行类语句创建的特定对象类型。类对象 被当作模板来创建实例对象,实例对象包含了特定于数据类型的数据(属性)和代码(方法)。
类可以基于一个或多个的其他类,称之为基类(ES),它继承基类的属性和方法,这样就可以通过继承来连续地细化对象模型。例如:您可能有一个 Mailbox 类提供邮箱的基本访问方法.,它的子类 MboxMailbox, MaildirMailbox, OutlookMailbox 用于处理各种特定邮箱格式。
什么是方法?
方法 实际上就是类定义中的函数。对于某个对象 x 上的方法,通常称为 x.name(arguments...) 。
class C:
def meth(self, arg):
return arg * 2 + self.attribute
什么是 self ?
Self 只是 方法 的第一个参数的常规名称。
例如:对于某个类的某个实例 x ,其方法 meth(self, a, b, c) 实际上应该被称为 x.meth(a, b, c) ;对于被调用的方法会被称为 meth(x, a, b, c) 。
另请参阅 为什么必须在方法定义和调用中显式使用“self”? 。
如何检查对象是否为给定类或其子类的一个实例?
可使用内置函数 isinstance(obj, cls)。 你可以提供一个元组而不是单个类来检查某个对象是否为任意多个类当中某一个类的实例,例如 isinstance(obj, (class1, class2, ...)),也可以检查某个对象是否为 Python 内置类型当中某一个类型的对象,例如 isinstance(obj, str) 或 isinstance(obj, (int, float, complex))。
请注意大多数程序不会经常对用户自定义类使用 isinstance()。 如果是你自已开发的类,更正确的面向对象风格是在类中定义方法来封装特定的行为,而不是检查对象的类并根据它属于什么类来做不同的事。 例如,如果你有一个执行某些操作的函数:
def search(obj):
if isinstance(obj, Mailbox):
… # code to search a mailbox
elif isinstance(obj, Document):
… # code to search a document
elif …
更好的方法是在所有类上定义一个 search() 方法,然后调用它:
class Mailbox:
def search(self):
… # code to search a mailbox
class Document:
def search(self):
… # code to search a document
什么是委托?
委托是一种面向对象的技巧(也称为设计模式)。 假设您有一个对象 x 并且想要改变其中一个方法的行为。 您可以创建一个新类,它提供您感兴趣的方法的新实现,并将所有其他方法委托给 x 的相应方法。
Python程序员可以轻松实现委托。 例如,以下类实现了一个类,该类的行为类似于文件,但将所有写入的数据转换为大写:
class UpperOut:
**def** __init__(self, outfile):<br /> self._outfile = outfile
**def** write(self, s):<br /> self._outfile.write(s.upper())
**def** __getattr__(self, name):<br /> **return** getattr(self._outfile, name)<br />在这里 `UpperOut` 类重新定义了 `write()` 方法在调用下层的 `self._outfile.write()` 方法之前将参数字符串转换为大写形式。 所有其他方法都被委托给下层的 `self._outfile` 对象。 委托是通过 `__getattr__` 方法来完成的;请参阅 [语言参考](https://docs.python.org/zh-cn/3/reference/datamodel.html#attribute-access) 了解有关控制属性访问的更多信息。<br />请注意对于更一般的情况来说,委托可能包含更多细节问题。 当某些属性既需要读取又需要设置时,类还必须定义 [`__setattr__()`](https://docs.python.org/zh-cn/3/reference/datamodel.html#object.__setattr__) 方法,并且这样做必须小心谨慎。 [`__setattr__()`](https://docs.python.org/zh-cn/3/reference/datamodel.html#object.__setattr__) 的基本实现大致相当于以下代码:<br />**class** **X**:<br /> ...<br /> **def** __setattr__(self, name, value):<br /> self.__dict__[name] = value<br /> ...<br />大多数 [`__setattr__()`](https://docs.python.org/zh-cn/3/reference/datamodel.html#object.__setattr__) 实现必须修改 `self.__dict__` 来为自身保存局部状态而又不至于造成无限递归。
如何从覆盖基类的派生类调用基类中定义的方法?
使用内置的 super() 函数:
class Derived(Base):
def meth(self):
super(Derived, self).meth()
对于 Python 3.0之前的版本,您可能正在使用经典类:对于诸如 class Derived(Base): ... 之类的类定义,可以将在 Base (或 Base 中的一个的基类)中定义的方法 meth() 调用为 Base.meth(self, arguments...) 。这里, Base.meth 是一个未绑定的方法,因此您需要提供 self 参数。
如何组织代码以便更改基类?
可以为基类定义别名,在类定义之前为其分配实际基类,并在整个类中使用别名。然后更改分配给别名的值,就能实现上述要求。顺便提一下,如果你想动态决定(例如,取决于资源的可用性)要使用哪个基类,这个技巧也很方便。例如:
BaseAlias =
class Derived(BaseAlias):
def meth(self):
BaseAlias.meth(self)
…
如何创建静态类数据和静态类方法?
Python支持静态数据和静态方法(在C ++或Java的意义上)。
对于静态数据,只需定义一个类属性。要为属性分配新值,就必须在赋值中显式使用类名:
class C:
count = 0 # number of times C.init called
**def** __init__(self):<br /> C.count = C.count + 1
**def** getcount(self):<br /> **return** C.count _# or return self.count_<br />对于任意 `c` 来说只要 `isinstance(c, C)` 为真,则 `c.count` 同样也指向 `C.count`,除非被 `c` 自身,或者从 `c.__class__` 回到 `C` 的基类搜索路径上的某个类所重载。<br />注意:在 C 的某个方法内部,像 `self.count = 42` 这样的赋值将在 `self` 自身的字典中新建一个名为 "count" 的不相关实例。 想要重新绑定类静态数据名称就必须总是指明类名,无论是在方法内部还是外部:<br />C.count = 314<br />静态方法是可行的:<br />**class** **C**:<br /> **@staticmethod**<br /> **def** static(arg1, arg2, arg3):<br /> _# No 'self' parameter!_<br /> ...<br />然而,获得静态方法效果的更直接的方法是通过一个简单的模块级函数:<br />**def** getcount():<br /> **return** C.count<br />如果您的代码是结构化的,以便为每个模块定义一个类(或紧密相关的类层次结构),那么这就提供了所需的封装。
如何在Python中重载构造函数(或方法)?
这个答案实际上适用于所有方法,但问题通常首先出现在构造函数的上下文中。
在C ++中,你会这样写
class C {
C() { cout << “No arguments\n“; }
C(int i) { cout << “Argument is “ << i << “\n“; }
}
在Python中,您必须编写一个构造函数,使用默认参数捕获所有情况。例如:
class C:
def init(self, i=None):
if i is None:
print(“No arguments”)
else:
print(“Argument is”, i)
这不完全等同,但在实践中足够接近。
你也可以尝试一个可变长度的参数列表,例如:
def init(self, *args):
…
相同的方法适用于所有方法定义。
我尝试使用 spam ,但是得到一个关于 _SomeClassNamespam 的错误信息。
以双下划线打头的变量会被“更名”以提供一种定义类私有变量的简单而有效的方式。 任何形式为 __spam 的标识符(至少前缀两个下划线,至多后缀一个下划线)文本会被替换为 _classname__spam,其中 classname 为去除了全部前缀下划线的当前类名称。
这并不能保证私密性:外部用户仍然可以访问 “classnamespam” 属性,私有变量值也在对象的 `dict_` 中可见。 许多 Python 程序员从来都不使用这种私有变量名称。
类定义了 del 方法,但是删除对象时没有调用它。
这有几个可能的原因。
del 语句不一定调用 __del__() —— 它只是减少对象的引用计数,如果(引用计数)达到零,才会调用 __del__()。
如果数据结构包含循环链接(例如,每个子级都有一个父级引用,每个父级都有一个子级列表的树),则引用计数将永远不会返回零。尽管Python 偶尔会运行一个算法来检测这样的循环,但在数据结构的引用计数清零后,垃圾收集器可能需要一段时间来运行,因此 __del__() 方法可能会在不方便和随机的时间被调用。这对于重现一个问题,是非常不方便的。更糟糕的是,对象 __del__() 的方法执行顺序是任意的。虽然可以运行 gc.collect() 来强制回收,但在一些病态的情况下,对象永远不会被回收。
尽管有循环收集器,但在对象上定义一个显式的 close() 方法以便在用完之后调用它仍然是一个好主意。 这样 close() 方法可以随即删除引用子对象的属性。 不要直接调用 __del__() —— 应该由 __del__() 调用 close(),并且 close() 能确保可以被同一对象多次地调用。
另一种避免循环引用的方法是使用 weakref 模块,该模块允许您指向对象而不增加其引用计数。例如,树状数据结构应该对其父级和同级引用使用弱引用(如果需要的话!)
最后,如果 __del__() 方法引发异常,会将警告消息打印到 sys.stderr 。
如何获取给定类的所有实例的列表?
Python不跟踪类(或内置类型)的所有实例。您可以对类的构造函数进行编程,以通过保留每个实例的弱引用列表来跟踪所有实例。
为什么 id() 的结果看起来不是唯一的?
id() 返回一个整数,该整数在对象的生命周期内保证是唯一的。因为在CPython中,这是对象的内存地址,所以经常发生在从内存中删除对象之后,下一个新创建的对象被分配在内存中的相同位置。这个例子说明了这一点:
>>>
>>> id(1000)
13901272
>>> id(2000)
13901272
这两个id属于之前创建的不同整数对象,并在执行 id() 调用后立即删除。要确保要检查其id的对象仍处于活动状态,请创建对该对象的另一个引用:
>>>
>>> a = 1000; b = 2000
>>> id(a)
13901272
>>> id(b)
13891296
模块
如何创建 .pyc 文件?
当一个模块首次被导入时(或自当前已编译文件创建后源文件被修改时),将会在对应 .py 文件所在目录的 __pycache__ 子目录下创建一个包含已编译代码的 .pyc 文件。 该 .pyc 文件的文件名的开头部分将与对应 .py 文件名相同,并以 .pyc 为后缀,中间部门则是基于创建它的特定 python 二进制代码版本。 (详情参见 PEP 3147。)
无法创建 .pyc 文件的可能原因是包含源文件的目录存在权限问题,这意味着 __pycache__ 子目录无法被创建。 举例来说,如果你以某一用户来开发程序但以另一用户身份来运行程序时就可能发生问题,测试 Web 服务器就属于这种情况。
除非设置了 PYTHONDONTWRITEBYTECODE 环境变量,否则当你导入模块并且 Python 具有创建 __pycache__ 子目录并将已编译模块写入该子目录的能力(权限、存储空间等等)时就会自动创建 .pyc 文件。
在最高层级运行的 Python 脚本不被视为导入,因此不会创建 .pyc 文件。 例如,如果你有一个最高层级模块文件 foo.py,它又导入了另一个模块 xyz.py,当你运行 foo 模块 (通过输入终端命令 python foo.py),则将为 xyz 创建一个 .pyc,因为 xyz 是被导入的,但不会为 foo 创建 .pyc 文件,因为 foo.py 不是被导入的。
如果你需要为 foo 创建 .pyc 文件 —— 即为不是被导入的模块创建 .pyc 文件 —— 你可以使用 py_compile 和 compileall 模块。py_compile 模块能够手动编译任意模块。 一种做法是交互式地使用该模块中的 compile() 函数:
>>>
>>> import py_compile
>>> pycompile.compile(‘foo.py’)
这将会将 .pyc 文件写入与 foo.py 相同位置下的 `_pycache子目录(或者你也可以通过可选参数cfile来重载该行为)。<br />你还可以使用 [compileall](https://docs.python.org/zh-cn/3/library/compileall.html#module-compileall) 模块自动编译一个目录或多个目录下的所有文件。 具体做法可以是在命令行提示符中运行compileall.py` 并提供包含要编译 Python 文件的目录路径:
python -m compileall .
如何找到当前模块名称?
模块可以通过查看预定义的全局变量 __name__ 找到自己的模块名称。如果它的值为 '__main__' ,程序将作为脚本运行。通常,通过导入使用的许多模块也提供命令行界面或自检,并且只在检查 __name__ 之后,才执行之后的代码:
def main():
print(‘Running test…’)
…
如何让模块相互导入?
假设您有以下模块:
foo.py:
from bar import bar_var
foo_var = 1
bar.py:
from foo import foo_var
bar_var = 2
问题是解释器将执行以下步骤:
- 首先导入foo
- 创建用于foo的空全局变量
- foo被编译并开始执行
- foo 导入 bar
- 创建了用于bar 的空全局变量
- bar被编译并开始执行
- bar导入foo(这是一个空操作(no-op ),因为已经有一个名为foo的模块)
- bar.foo_var = foo.foo_var
最后一步失败了,因为Python还没有解释foo,而foo的全局符号字典仍然是空的。
当你使用 import foo ,然后尝试在全局代码中访问 foo.foo_var 时,会发生同样的事情。
这个问题有(至少)三种可能的解决方法。
Guido van Rossum 建议避免使用 from <module> import ... ,并将所有代码放在函数中。全局变量和类变量的初始化只能使用常量或内置函数。这意味着导入模块中的所有内容都被引用为 <module>.<name> 。
Jim Roskind建议在每个模块中按以下顺序执行步骤:
- 导出(全局变量,函数和不需要导入基类的类)
导入声明- 活动代码(包括从导入值初始化的全局变量)。
van Rossum不喜欢这种方法,因为导入出现在一个陌生的地方,但这种方法确实有效。
Matthias Urlichs建议重构代码,以便首先不需要递归导入。
这些解决方案并不相互排斥。
import(‘x.y.z’) 返回 ; 如何获取z?
考虑使用 importlib 中的函数 import_module() :
z = importlib.import_module(‘x.y.z’)
当我编辑了导入过的模块并重新导入它时,这些变化没有显示出来。为什么会这样?
出于效率和一致性的原因,Python仅在第一次导入模块时读取模块文件。如果不这么做,在一个由许多模块组成的程序中,每个模块都会导入相同的基本模块,那么基本模块将被解析和重新解析多次。要强制重新读取已更改的模块,请执行以下操作:
import importlib
import modname
importlib.reload(modname)
警告:这种技术不是100%万无一失。特别是包含如下语句的模块
from modname import someobjects
将继续使用旧版本的导入对象。如果模块包含类定义,则不会更新现有的类实例以使用新的类定义。这可能导致以下矛盾行为:
>>>
>>> import importlib
>>> import cls
>>> c = cls.C() # Create an instance of C
>>> importlib.reload(cls)
>>> isinstance(c, cls.C)
False
如果打印出类对象的“标识”,问题的本质就会明确:
>>>
>>> hex(id(c._class))
‘0x7352a0’
>>> hex(id(cls.C))
‘0x4198d0’
