Funtioncal Programming Tools

builtins

  • filter(iterable, func):过滤func返回布尔否值元素
  • enumerate(iterable):添加索引迭代
  • zip(*iterables):打包迭代元素
  • map(func, *iterables)func接受各迭代器中元素作为 参数调用(类似zip
  • iter:两种产生迭代器的模式
    • iter(iterable):返回迭代器
    • iter(callable, sentinel):调用callable直到返回 sentinel停止迭代(不包括)
      • 可用于替代while循环

itertools

itertools:包含为高效循环而创建迭代器的函数

无穷迭代器

  • count(start, step=1):步长累加
  • cycle(p):循环p中元素
  • repeat(elem, n=None):重复elem

迭代元素处理

  • accumulate(p, func=None):累加p中元素
  • chain(*iters):链接迭代器
  • chain.from_iterable(iterable):链接可迭代对象中迭代器
  • compress(data, selelctors):根据selectors选取data
  • dropwhile(pred, seq):保留首个满足pred之后所有元素
  • takewhile(pred, seq):保留首个不满足pred之前元素
  • filterfalse(pred, seq):反filter
  • groupby(iterable, key=None):根据key(v)值分组迭代器
  • islice(seq, start=0, stop=None, step=1):切片
  • starmap(func, seq):迭代对seq中执行func(*elem)
  • tee(iterable, n=2):复制迭代器,默认2个
  • zip_longes(*iters, fillvalue):依最长迭代器zip,较短 循环填充或fillvalue填充

排列组合

  • product(*iters, repeat=1):笛卡尔积
  • permutations(p, r=None)r长度的排列,缺省全排列
  • combinations(p, r)r长度组合
  • combinations_with_replacement(p,r):可重复组合

functools

functools:包含高阶函数,即参数、返回值为其他函数的函数

函数转换

  • cmp_to_key(func):将旧式比较函数转换新式key function

    • 常用在以下函数key参数中转换旧式比较函数
      • sorted
      • min
      • max
      • heapq.nlargest
      • heapq.nsmallest
      • itertools.groupby
    • key function:接收参数,返回可以用于排序的值
  • partial(func, *args, **kwargs):返回partial对象,其 调用时类似使用argskwargs调用func

  • partial.method(func, *args, **kwargs):适合类命名空间 中函数、描述器

  • update_wrapper(wrapper, wrapped, assigned=WRAPPER_ASSIGNMENTS, udpated=WRAPPER_UPDATES): 更新wrapper函数信息为wrapped函数信息

函数应用

  • reduce(func, iterable[, initializer]):使用func接受 两个参数,reduce处理iterable

函数装饰器

  • @lru_cache(maxsize=128, typed=False):缓存函数执行结果

    • 字典存储缓存:函数固定参数、关键字参数必须可哈希
    • 不同参数调用模式(如仅参数顺序不同)可能被视为不同 ,从而产生多个缓存项
    • 可以用于方便实现动态规划
  • @singledispatch:转换函数为单分派范型函数,实现python 的重载(接口多态)

    • single dispatch:单分派,基于单个参数类型分派的 范型函数分派形式
  • @wraps(wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES): 等价于partial(update_wrapper, wrapped, assigned, updated)

类装饰器

  • @total_ordering:根据类中已定义比较方法实现剩余方法
    • 类必须实现__eq__、其他富比较方法中任意一种

operator

operator:提供与python内置运算符对应的高效率函数

  • 为向后兼容,保留双下划线版本函数名(同特殊方法名)

比较运算

  • lt(a,b)/__lt__(a,b)
  • le(a,b)
  • eq(a,b)
  • ne(a,b)
  • ge(a,b)
  • gt(a,b)

逻辑运算

  • not(obj)
  • truth(obj):等价于使用bool构造器
  • is_(a,b)
  • is_not(a,b)

数值运算

  • add(a,b)
  • sub(a,b)
  • mul(a,b)
  • div(a,b)
  • pow(a,b)
  • mod(a,b)
  • floordiv(a,b)
  • truediv(a,b)
  • matmul(a,b)
  • abs(obj)
  • neg(obj)
  • pos(obj)

在位赋值

  • 在位运算对可变数据类型才会更新参数,否则只返回结果
  • iadd(a,b):等价于a += b
  • isub(a,b)
  • imul(a,b)
  • idiv(a,b)
  • ipow(a,b)
  • imod(a,b)
  • ifloordiv(a,b)
  • itruediv(a,b)
  • imatmul(a,b)

位运算

  • and_(a,b)
  • or_(a,b)
  • xor(a,b)
  • inv(obj)/invert(obj)
  • lshift(a,b)
  • shift(a,b)

在位运算

  • iand(a,b)
  • ior(a,b)
  • ixor(a,b)

索引

  • index(a)

序列运算

  • concat(a,b)
  • contains(a,b)
  • countOf(a,b)
  • delitem(a,b)
  • getitem(a,b)
  • indexOf(a,b)
  • setitem(a,b,c)
  • length_hint(obj, default=0)

访问函数

  • attrgetter(attr)/attrgetter(*attrs):返回函数,函数 返回值为参数属性attr
  • itemgetter(item)/itemgetter(*items):类似
  • methodcaller(name[, args...]):类似

Python数据类型

collections

array

headq

bisect

weakref

datetime

calender

types

copy

pprint

reprlib

enum

Python概述

综述

  • 语言的具体实现可能发生改变、其他实现可能使用不同方式
  • 在语言的参考文档中加入过多细节实现很危险

Python实现

python只是一种语言,其具体解释器实现有很多种

  • CPython:C语言实现,最原始版本

    • 通常就被称为Python,其他实现区分时才强调为CPython
    • 新语言特性通常较早出现
  • Jython:Java实现

    • 将Python代码编译为Java字节码
    • 可以左线Java应用的脚本语言、创建需要Java类库支持的 应用
    • 在JVM上运行
  • Python for .NET:实际上使用CPython实现,但是属于.NET 托管应用,可以引入.NET类库

  • IronPython:.NET实现

    • 生成IL的完全Python实现,将Python代码直接编译为.NET 程序集
  • PyPy:RPython(Python语言子集)实现

    • JIT编译器,执行效率高于CPython
    • 非栈式支持
    • 允许方便修改解释器,鼓励对语言本身进行实验
  • CPython是解释器实现版本,cython是将Python代码翻译为 C插件的项目/包

Notation说明

标注:词法、句法解析的描述使用修改过的BNF语法标注

1
2
name ::= lc_letter(lc_letter | "_")*
lc_letter ::= "a"..."z"
  • ::=:声明规则,左侧为规则名称
  • |:分隔可选项
  • *:前一项的零次、多次重复
  • +:前一项的一次、多次重复
  • []:括起内容可选,即出现零次、一次
  • ():分组
  • "":固定字符串包含在引号内
  • :空格仅用于分隔token
  • ...:三个点分割的本义字符表示在指定区间范围内的任意 单个字符
  • <>:对所定义符号的非常描述,在必要时用于说明“控制字符” 意图
  • 每条规则通常为一行,多个可选项规则可用|为界分为多行
  • 词法定义:作用域输入源中的单独字符
  • 句法定义:作用于词法分析生成的token stream

约定

  • 实例方法:定义在类命名空间中、未因访问而绑定函数
  • 绑定方法:已绑定实例方法
  • 静态方法
  • 类方法
  • [类]实例:类实例化所得对象
  • 对象:泛指所有python对象,包括类、实例

Global Intepretor Lock

全局内存锁:GIL,任何python字节码执行前必须获得的解释器锁

  • 在任何时刻,只能有一个线程处于工作状态
  • 避免多个线程同时操作变量导致内存泄漏、错误释放

优势

  • GIL实现简单,只需要管理一把解释器锁就能保证线程内存安全

    • 当然GIL只能保证引用计数正确,避免由此导致内存问题
    • 还需要原子操作、对象锁避免并发更新问题
  • GIL单线程情况下性能更好、稳定,若通过给所有对象引用计数 加锁来实现线程安全

    • 容易出现死锁
    • 性能下降很多
  • 方便兼容C遗留库,这也是python得以发展的原因

    • 很多python需要的C库扩展要求线程安全的内存管理

影响

  • Python线程是真正的操作系统线程

    • 在准备好之后必须获得一把共享锁才能运行
    • 每个线程都会在执行一定机器指令和后切换到无锁状态, 暂停运行
    • 事实上程序在开始时已经在运行“主线程”
    • 解释器检查线程切换频率sys.getcheckinterval()
  • Python线程无法在多核CPU间分配,对CPU-Bound程序基本没有 提升效果,对于IO-Bound的程序性能仍然有巨大帮助

解决方案

  • 多进程

    • 进程分支:os.fork
    • 派生进程:multiprocessing.Processconcurrent.futures
  • C语言库封装线程:ctypescython

    • C扩展形式实现任务线程可在python虚拟机作用域外运行 可以并行运行任意数量线程
    • 在运行时释放GIL、结束后继续运行python代码时重新获取 GIL,真正实现独立运行
  • 使用其他版本Python解释器:只有原始Python版本CPython使用 GIL实现

    • Jython
    • IronPython
    • PyPy

Python最高层级组件

完整的Python程序

  • 完整的python程序会在最小初始化环境中被执行

    • 所有内置、标准模块均可用,但均处于未初始化状态
    • 只有sysbuiltins__main__已经初始化
    • __main__模块为完整程序的执行提供局部、全局命名空间
  • 完整程序可通过三种形式传递给解释器

    • -c命令行选项传递字符串
    • 文件作为第一个命令行参数
    • 标准输入
    • 若文件、标准输入是tty设备,解释器进入交互模式,否则 将文件当作完整程序执行
  • 解释器也可以通过交互模式被发起调用

    • 每次读取执行一条语句,语句会在__main__命名空间中 被执行
    • 初始环境同完整程序

输入类型

文件输入

文件输入:从非交互式文件读取的输入,具有相同形式

1
file_input ::= (NEWLINE|statement)*
  • 适合以下几种情况

    • 解析完整的python程序(从文件、字符串)
    • 解析模块
    • 解析传递给exec()函数的字符串

交互式输入

交互式输入:从tty设备读取输入

1
interactive_input ::= [stmt_list] NEWLINE | compound_stmt NEWLINE
  • 注意
    • 交互模式中(最高层级)复合语句后必须带有空行,帮助 解释器确定输入的结束

表达式输入

表达式输入

1
eval_input ::= expression_list NEWLINE*
  • eval被用于表达式输入
  • 忽略开头空白

Python包、模块

综述

导入方式

importing操作可以使得模块能够访问其他模块中代码

  • import:结合了以下两个操作,发起导入调机制最常用方式

    • 搜索指定名称模块:对__import__()带有适当参数调用
    • 将搜索结果绑定到当前作用域中名称:__import__返回值 被用于执行名称绑定操作
  • __import__():只执行模块搜索、找到模块后创建module

    • 可能产生某些副作用
      • 导入父包
      • 更新各种缓存:sys.modules
  • importlib.import_module()

    • 可能会选择绕过__import__,使用其自己的解决方案实现 导入机制
    • 用于为动态模块导入提供支持
    • importlib模块参见标准库

子模块

  • 任意机制加载子模块时,父模块命名空间中会添加对子模块对象 的绑定

Packages

  • python只有一种模块对象类型:所有模块都属于该类型,C、 Python语言实现均是

  • 包:为帮助组织模块、并提供名称层次结构引入

    • 可将包视为文件系统中目录、模块视为目录中文件, 但包、模块不是必须来自文件系统
    • 类似文件系统,包通过层次结构进行组织:包内包括模块、 子包
  • 所有包都是模块,但并非所有模块都是包

    • 包是一种特殊的模块
    • 特别的,任何具有__path__属性的模块都被当作包
  • 所有模块都有自己的名字

    • 子包名与其父包名以.分隔,同python标准属性访问语法

Regular Packages

正规包:通常以包含__init__.py文件的目录形式出现

  • __init__.py文件可以包含和其他模块中包含python模块相似 的代码

  • 正规包被导入时

    • __init__.py文件会隐式被执行,其中定义对象被绑定到 该包命名空间中名称
    • python会为模块添加额外属性

Namespace Packages

命名空间包:由多个部分构成,每个部分为父包增加一个子包

  • 包各部分可以物理不相邻,不一定直接对应到文件系统对象, 可能是无实体表示的虚拟模块

    • 可能处于文件系统不同位置
    • 可能处于zip文件、网络上,或在导入期间其他可搜索位置
  • __path__属性不是普通列表,而是定制的可迭代类型

    • 若父包、或最高层级包sys.path路径发生改变,对象会 在包内的下次导入尝试时,自动执行新的对包部分的搜索
  • 命名空间包中没有__init__.py文件

    • 毕竟可能有多个父目录提供包不同部分,彼此物理不相邻
    • python会在包、子包导入时为其创建命名空间包

导入相关模块属性

  • 以下属性在加载时被设置,参见 cs_python/py3ref/import_system
  • __name__:模块完整限定名称,唯一标识模块

  • __loader__:导入系统加载模块时使用的加载器对象

    • 主要用于内省
    • 也可用于额外加载器专用功能
  • __package__:取代__name__用于主模块计算显式相对 导入

    • 模块为包:应设置为__name__
    • 模块非包:最高层级模块应设为空字符串,否则为父包名
    • 预期同__spec__.parent值相同,未定义时,以 __spec__.parent作为回退项
  • python <PYSCIRPT>直接执行脚本时__name__被设置为 __main____package__设置为None,此时导入器无法 解释相对导入中.,相对导入报错
  • python -m <PYSCIRPT>则会按模块逻辑设置__name____package__,相对导入可以正常执行

__spec__

__spec__:导入模块时要使用的模块规格说明

  • __spec__正确设置将同时作用于解释器启动期间 初始化的模块
  • __main__某些情况下被设置为None

__path__

  • 具有该属性模块即为包:包模块必须设置__path__属性, 非包模块不应设置

  • 在导入子包期间被使用,在导入机制内部功能同sys.path, 即用于提供模块搜索位置列表

    • 但受到更多限制,其必须为字符串组成可迭代对象,但若其 没有进一步用处可以设置为空
    • 适用作用于sys.path的规则
    • sys.path_hooks会在遍历包的__path__时被查询
  • 可在包的__init__.py中设置、更改

    • PEP420引入之后,命名空间包不再需要提供仅包含操作 __path__代码的__init__.py文件,导入机制会自动为 命名空间包正确设置__path__
    • 在之前其为实现命名空间包的典型方式

__repr__

  • 若模块具有__spec__,导入机制将尝试使用其中规格信息生成 repr

    • name
    • loader
    • origin
    • has_location
  • 若模块具有__file__属性,将被用作repr的一部分

  • 否则若模块具有__loader__属性且非None,则加载器repr 将被用作模块repr的一部分

  • 其他情况下,仅在repr中适用模块的__name__

  • 可以在模块规则说明中显式控制模块对象repr

__file__/__cached__

  • __file__:模块对应的被加载文件的路径名
  • __cached__:编译版本代码(字节码文件)路径
  • __file__为可选项,须为字符串

    • 可以在其无语法意义时不设置
    • 对从共享库动态加载的扩展模块,应为共享库文件路径名
  • __cached__

    • 不要求编译文件已经存在,可以表示应该存放编译文件 的位置
    • 不要求__file__已经设置
      • 有时加载器可以从缓存加载模块但是无法从文件加载
      • 加载静态链接至解释器内部的C模块
  • .pyc文件加载缓存字节码前会检查其是否最新

    • 默认通过比较缓存文件中保存的源文件修改时间戳实现
    • 也支持基于哈希的缓冲文件,此时.pyc文件中保存源文件 哈希值
      • 检查型:求源文件哈希值再和缓存文件中哈希值比较
      • 非检查型:只要缓存文件存在就直接认为缓存文件有效
    • --check-hash-based-pycs命名行选项设置基于哈希的 .pyc文件有效性

执行相关模块属性

  • __doc__:模块文档字符串
  • __annotaion__:包含变量标注的字典
    • 在模块体执行时获取
  • __dict__:以字典对象表示的模块命名空间
  • CPython:由于CPython清理模块字典的设定,模块离开作用域时 模块字典将被清理,即使字典还有活动引用,可以复制该字典、 保持模块状态以直接使用其字典

sys.modules模块缓存

sys.modules映射:缓存之前导入的所有模块(包括中间路径) (即导入子模块会注册父模块条目)

  • 其中每个键值对就是限定名称、模块对象

  • 在其中查找模块名称

    • 若存在需要导入模块,则导入完成
    • 若名称对应值为Noneraise ModuleNotFoundError
    • 若找不到指定模块名称,python将继续搜索
  • 映射可写,可删除其中键值对

    • 不一定破坏关联模块,因为其他模块可能保留对其引用
    • 但是会使命名模块缓存条目无效,导致下次导入时重新 搜索命名模块,得到两个不同的两个模块对象
    • importlib.reload将重用相同模块对象,通过重新运行 模块代码重新初始化模块内容

Finders And Loaders

  • Finders:查找器,确定能否使用所知策略找到指定名称模块
  • Loaders:加载器,加载找到的指定模块
  • Importer:导入器,同时实现两种接口的对象,在确定能加载 所需模块时会返回自身
  • 导入机制通过import hooks实现扩展

    • 可以加入新的查找器以扩展模块搜索范围、作用域
  • 工作流程:在sys.modules缓存中无法找到指定名称模块时

    • 查找器若能找到指定名称模块,返回模块规格说明spec
    • 加载器将利用查找器返回的模块规格说明加载模块

Import Path

导入路径:文件系统路径、zip文件等path term组成的位置列表

  • 其中元素不局限于文件系统位置,可扩展为字符串指定的任意 可定位资源

    • URL指定资源
    • 数据库查询
  • 位置条目来源

    • 通常为sys.path
    • 对次级包可能来自上级包的__path__属性
  • 其中每个路径条目指定一个用于搜索模块的位置

    • path based finder将在其中查找导入目标

sys.path

sys.path:模块、包搜索位置的字符串列表

  • 初始化自PYTHONPATH环境变量、特定安装和实现的默认设置、 执行脚本目录(或当前目录)

  • 其中条目可以指定文件系统中目录、zip文件、可用于搜索模块 的潜在位置

  • 只能出现字符串、字节串,其他数据类型被忽略

    • 字节串条目使用的编码由导入路径钩子、 path entry finder确定
  • 所以可以修改sys.path值定制导入路径,CPython实现参见 cs_python/py3ref/import_system

sys.path_import_cache

sys.path_importer_cache:存放路径条目到路径条目查找器映射 的缓存

  • 减少查找路径条目对应路径条目查找器的消耗,对特定路径条目 查找对应路径条目查找只需进行一次

  • 可从中移除缓存条目,以强制基于路径查找器执行路径条目搜索

Import Hooks

  • meta hooks:元[路径]钩子

    • 导入过程开始时被调用,此时仅sys.modules缓存查找 发生,其他导入过程未发生
    • 所以允许元钩子重载sys.path过程、冻结模块甚至内置 模块
    • 元钩子即导入器/元路径查找器
    • sys.meta_path为元路径查找器列表,可在其中注册定制 元钩子
  • path[ entry] hooks:导入路径钩子

    • sys.pathpackage.__path__处理的一部分
    • 基于路径的查找器调用其处理路径条目,以获取路径条目 查找器
    • 导入路径钩子返回路径条目查找器
    • sys.path_hooks为导入路径钩子列表,可在其中注册 定制导入路径钩子

默认元路径查找器/导入器

python默认实现sys.meta_path有以下导入器(元路径查找器)

  • BuiltinImporter:定位、导入内置模块
  • FrozenImporter:定位、导入冻结模块
  • PathFinder:定位、导入来自import path中模块
  • 尝试导入模块时,内置模块、冻结模块导入器优先级较高,所以 解释器首先搜索内置模块

Finder

  • 指定名称模块在sys.modules找不到时,python继续搜索 sys.meta_path,按顺序调用其中元路径查找器

  • sys.meta_path处理到列表末尾仍未返回说明对象,则 raise ModuleNotFoundError

  • 导入过程中引发的任何异常直接向上传播,并放弃导入过程
  • 对非最高层级模块的导入请求可能会多次遍历元路径

Meta Path Finders

元路径查找器:

  • 元路径查找器可使用任何策略确定其是否能处理给定名称模块

    • 若知道如何处理指定名称的模块,将返回模块规格说明
    • 否则返回None
  • 模块规格协议:元路径查找器应实现find_spec()方法

    • 接受名称、导入路径、目标模块作为参数
    • 返回模块规格说明

Spec

  • 模块规格[说明]:基于每个模块封装的模块导入相关信息

    • 模块规格中大部分信息对所有模块是公用的
    • 模块规格说明作为模块对象的__spec__属性对外公开
  • 用途

    • 允许状态在导入系统各组件间传递,如:查询器和加载器
    • 允许导入机制执行加载的样板操作,否则该由加载器负责

find_spec

1
2
def finder.find_spec(fullname, path=None, target=None):
pass
  • fullname:被导入模块的完整限定名称
  • path:供模块搜索使用的路径条目
    • 对最高层级模块应为None
    • 对子模块、子包应为父包__path__属性值,若 相应__path__属性无法访问将 raise ModuleNotFoundError
  • target:将被作为稍后加载目标的现有模块对象
    • 导入系统仅在重加载期间传入目标模块
  • 导入器的find_spec()返回模块规格说明中加载器为self
  • 有些元路径查找器仅支持顶级导入,path参数不为None时 总返回None

Loaders

  • 模块规格说明被找到时,导入机制将在加载该模块时使用
    • 其中包含的加载器将被使用,若存在

加载流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
module = None

if spec.loader is not None and hasattr(spec.loader, 'create_module'):
# 模块说明中包含加载器,使用加载器创建模块
module = spec.loader.create_module(spec)

if module is None:
# 否则创建空模块
module = types.ModuleType(spec.name)

# 设置模块导入相关属性
_init_module_attrs(spec, module)

if spec.loader is None:
# 模块说明中不包含加载器
# 检查模块是否为为命名空间包
if spec.submodule_search_locations is not None:
# 设置`sys.modules`
sys.modules[spec.name] = module
else:
raise ImportError
elif not hasattr(spec.loader, "exec_module"):
# 向下兼容现有`load_module`
module = spec.loader.load_module(spec.name)
else:
sys.modules[spec.name] = module
try:
# 模块执行
spec.loader.exec_module(module)
except BaseException:
try:
# 加载模块失败则从`sys.modules`中移除
del sys.modules[spec.name]
except KeyError:
pass
raise
return sys.modules[spec.name]
  • 创建模块对象
  • 设置模块导入相关属性:在执行模块代码前设置
  • sys.modules注册模块
  • 模块执行:模块导入关键,填充模块命名空间

create_module创建模块对象

  • 模块加载器可以选择通过实现create_module方法在加载 期间创建模块对象

    • 其应接受模块规格说明作为参数
  • 否则导入机制使用types.ModuleType自行创建模块对象

sys.modules注册模块

  • 在加载器执行代码前注册,避免模块代码导入自身导致无限 递归、多次加载
  • 若模块为命名空间包,直接注册空模块对象

exec_module模块执行

  • 导入机制调用importlib.abc.Loader.exec_module()方法执行 模块对象

    • CPython:exec_module不定返回传入模块,其返回值将被 忽略
      • importlib避免直接使用返回值,而是通过在 sys.modules中查找模块名称获取模块对象
      • 可能会间接导致被导入模块可能在sys.modules中 替换其自身
  • 加载器应该该满足

    • 若模块是python模块(非内置、非动态加载),加载器应该 在模块全局命名空间module.__dict__中执行模块代码

    • 若加载器无法执行指定模块,则应raise ImportError, 在exec_module期间引发的任何其他异常同样被传播

  • 加载失败时作为附带影响被成功加载的模块仍然保留

    • 重新加载模块会保留加载失败模块(最近成功版本)

Path Based FinderPathFinder

基于路径的查找器:在特定path entry中查找、加载指定的python 模块、包

  • 基于路径查找器只是遍历import path中的路径条目,将其 关联至处理特定类型路径的path entry finder

  • 默认路径条目查找器集合实现了在文件系统中查找模块的所有 语义,可以处理多种文件类型

    • python源码.py
    • python字节码.pyc
    • 共享库.so
    • zip包装的上述文件类型(需要zipimport模块支持)
  • 作为元路径查找器

    • 实现有find_spec协议
    • 并提供额外的钩子、协议以便能扩展、定制可搜索路径条目 的类型,定制模块从import path的查找、加载

流程

导入机制调用基于路径的查找器的find_spec()迭代搜索 import path的路径条目,查找对应路径条目查找器

  • 先在sys.path_impporter_cache缓存中查找对应路径条目 查找器

  • 若没有在缓存中找到,则迭代调用sys.path_hooksPath Entry Hook

  • 迭代结束后若没有返回路径条目查找器,则

    • sys.path_importer_cache对应值为None
    • 返回None,表示此元路径查找器无法找到该模块

当前目录

对空字符串表示的当前工作目录同sys.path中其他条目处理方式 有所不同

  • 若当前工作目录不存在,则sys.path_importer_cache 中不存放任何值

  • 模块查找回对当前工作目录进行全新查找

  • sys.path_importer_cache使用、 importlib.machinery.PathFinder.find_spec()返回路径将是 实际当前工作目录而非空字符串

Path Entry Hook

路径条目钩子:根据路径条目查找对应路径条目查找器的可调用对象

  • 参数:字符串、字节串,表示要搜索的目录条目

    • 字节串的编码由钩子自行决定
    • 若钩子无法解码参数,应raise ImportError
  • 路径条目钩子返回值

    • 可处理路径条目的路径条目查找器
    • raise ImportError:表示钩子无法找到与路径条目对应 路径条目查找器
      • 该异常会被忽略,并继续对import path迭代

Path Entry FinderPathEntryFinder

路径条目查找器:

  • 元路径查找器作用于导入过程的开始,遍历sys.meta_path
  • 路径条目查找器某种意义上是基于路径查找器的实现细节

find_spec

1
2
def PathEntryFinder.find_spec(fullname, target=None):
pass
  • 路径条目查找器协议:目录条目查找器需实现find_spec方法

    • 以支持模块、已初始化包的导入
    • 给命名空间包提供组成部分
  • 参数

    • fullname:要导入模块的完整限定名称
    • target:目标模块
  • 返回值:完全填充好的模块规格说明

    • 模块规格说明总是包含加载器集合
    • 但命名空间包的规格说明中loader会被设置为None, 并将submodule_search_locations设置为包含该部分的 列表,以告诉导入机制该规格说明为命名空间包的portion
  • Portion:构成命名空间包的单个目录内文件集合
  • 替代旧式find_loader()find_module()方法

替换标准导入系统

  • 替换sys.meta_path为自定义元路径钩子

    • 替换整个导入系统最可靠机制
  • 替换内置__import__()函数

    • 仅改变导入语句行为而不影响访问导入系统其他接口
    • 可以在某个模块层级替换,只改变某块内部导入语句行为
  • 替换find_spec(),引发ModuleNotFoundError

    • 选择性的防止在元路径钩子导入某些模块

__main__

  • __main__模块是在解释器启动时直接初始化,类似sysbuiltins,但是不被归类为内置模块,因为其初始化的方式 取决于启动解释器的旗标(命令行参数)

__spec__

根据__main__被初始化的方式,__main__.__spec__被设置为 None或相应值

  • -m选项启动:以脚本方式执行模块

    • 此时__spec__被设置为相应模块、包规格说明

    • __spec__会在__main__模块作为执行某个目录、zip 文件、其他sys.path条目的一部分加载时被填充

    • 此时__main__对应可导入模块和__main__被视为不同 模块

  • 其余情况

    • __spec__被设置为None

    • 因为用于填充__main__的代码不直接与可导入模块相对应

      • 交互型提示
      • -c选项
      • 从stdin运行
      • 从源码、字节码文件运行
  • -m执行模块时sys.path首个值为空字符串,而直接执行脚本 时首个值为脚本所在目录

Import[ Search] Path定制

动态增加路径

1
2
3
import sys
sys.path.insert(1, /path/to/fold/contains/module)
# 临时生效,对不经常使用的模块较好

修改PYTHONPATH环境变量

1
2
 # .bashrc
export PYTHONPATH=$PYTHONPATH:/path/to/fold/contains/module
  • 对许多程序都使用的模块可以采取此方式
  • 会改变所有Python应用的搜索路径

增加.pth文件

/path/to/python/site-packages(或其他查找路径目录)下 添加.pth配置文件,内容为需要添加的路径

1
2
 # extras.pth
/path/to/fold/contains/module
  • 简单、推荐
  • python在遍历已知库文件目录过程中,遇到.pth文件会将其中 路径加入sys.path

表达式

Atoms

原子:表达式最基本元素

  • 最简单原子

    • 标识符
    • 字面值
  • 以圆括号、方括号、花括号包括的形式在语法上也被归为原子

1
2
atom      ::=  identifier | literal | enclosure
enclosure ::= parenth_form | list_display | dict_display | set_display | generator_expression | yield_atom

Indentifiers/Names

名称:作为原子出现的标识符

  • 名称被绑定到对象时:对原子求值将返回相应对象
  • 名称未绑定时:对原子求值将raise NameError

Private Name Mangling

  • 类的私有名称:文本形式出现在类定义中以两个、更多下划线 开头且不以两个、更多下划线结尾的标识符

私有名称转换:在为私有名称生成代码前,其被转换为更长形式

  • 转换方式:在名称前插入类名、下划线

    • 若转换后名称太长(超过255字符),某些实现中可能 发生截断
  • 转换独立于标识符使用的句法

  • 若类名仅由下划线组成,则不会进行转换

Literals

字面值:

1
literal ::=  stringliteral | bytesliteral | integer | floatnumber | imagnumber
  • 对字面值求值将返回改值对应类型的对象

    • 对浮点数、复数,值可能为近似值
  • 所有字面值都对应不可变数据类型

    • 所以对象标识的重要性不如其实际值
  • 多次对具有相同值的字面值求值,可能得到相同对象、或具有 相同值的不同对象

    • 元组是不可变对象,适用字面值规则:两次出现的空元组 产生对象可能相同、也可能不同

      todo

Parenthesized Forms

带括号形式:包含在()可选表达式列表

1
parenth_form ::= "(" [starred_expression] ")"
  • 带圆括号表达式列表将返回表达式所产生的任何东西

    • 内容为空的圆括号返回空元组对象
    • 列表包含至少一个逗号,产生元组
    • 否则,产生表达式列表对应的单一表达式
  • 元组不是由圆括号构建,实际是,逗号操作符起作用

    • 空元组是例外,此时圆括号必须,因为表达式中不带圆括号 的“空”会导致歧义

List/Set/Dict/Generator-Tuple

  • display:显式列出容器内容
  • comprehension:推导式,通过循环、筛选指令计算
1
2
3
4
comprehension ::=  expression comp_for
comp_for ::= ["async"] "for" target_list "in" or_test [comp_iter]
comp_iter ::= comp_for | comp_if
comp_if ::= "if" expression_nocond [comp_iter]
  • 推导式结构:单独表达式后加至少一个for子句以及零个、或 多个forif子句

    • forif子句视为代码块,按从左到右顺序嵌套 (类似for循环嵌套)
    • 每次到达最内层代码块时对表达式求值以产生元素
  • 除最左边for子句中可迭代表达式,推导式在另一个隐式嵌套 作用域内执行

    • 确保赋给目标列表的名称不会“泄露”到外层作用域
    • 最左边for子句中可迭代表达式直接在外层作用域中被 求值,然后作为参数传递给隐式嵌套作用域
    • 后续for子句、最左侧for子句中任何筛选条件不能在 外层作用域中被求值,因为其可能依赖于从最左侧可迭代 对象中获得的值
  • 为确保推导式总能得到类型正确的容器,隐式嵌套作用域内禁止 使用yieldyield from表达式,因为其会对外层作用域 造成附加影响

  • 若推导式包含async for子句、await表达式,则为异步 推导式

List Displays

列表显式:用[]方括号括起的、可能为空的表达式系列

1
list_display ::= "[" [starred_list | comprehesion] "]"
  • 列表显式会产生新的列表对象,内容通过表达式、推导式指定
  • 提供逗号分隔的表达式时:元素从左至右求值,按此顺序放入 列表对象
  • 提供推导式时:根据推导式产生结果元素进行构建

Set Displays

集合显式:用{}花括号标明,与字典区别在于没有冒号分隔键值

1
set_display ::=  "{" (starred_list | comprehension) "}"
  • 集合显式产生可变集合对象,内容通过表达式、推导式指定
  • 提供逗号分隔的表达式时:元素从左至右求值,按此顺序放入 列表对象
  • 提供推导式时:根据推导式产生结果元素进行构建
  • 空集合不能使用{}构建,此构建的是空字典

Dict Displays

字典显式:用{}花括号括起来的、可能为空的键值对

1
2
3
4
dict_display       ::=  "{" [key_datum_list | dict_comprehension] "}"
key_datum_list ::= key_datum ("," key_datum)* [","]
key_datum ::= expression ":" expression | "**" or_expr
dict_comprehension ::= expression ":" expression comp_for
  • **:映射拆包,操作数必须是映射
  • 字典显式产生新的字典对象

  • 提供,分隔键值对序列

    • 从左至右被求值以定义字典条目
    • 可多次指定相同键,最终值由最后给出键值对决定
  • 提供字典推导式

    • 以冒号分隔的两个表达式,后者带上标准forif子句
    • 作为结果键值对按产生顺序被加入新字典
  • 键类型需要为hashable

Generator Expression

生成器表达式:用圆括号括起来的紧凑形式生成器(迭代器)标注

1
generator_expression ::=  "(" expression comp_for ")"
  • 生成器表达式会产生新的生成器(迭代器)对象

    • 句法同推导式,但使用圆括号括起
    • 圆括号在只附带一个参数(省略expression)的调用中 可以被省略
  • 生成器表达式中使用的变量在生成器对象调用__next__方法 时以惰性方式被求值,同普通生成器

    • 最左侧for子句内可迭代对象会被立即求值,则其造成的 错误会在生成器表达式被定义时被检测到

Yield Expression

yield表达式:将控制权交还给调度程序

1
2
yield_atom       ::=  "(" yield_expression ")"
yield_expression ::= "yield" [expression_list | "from" expression]
  • yield:返回其后表达式求值
  • yield表达式是赋值语句右侧唯一表达式时,括号可以省略
  • 在定义生成器函数、异步生成器函数时才会用到,也只能在函数 定义内部使用yield表达式,将函数变为(异步)生成器函数

  • yield表达式会对外层作用域造成附带影响,不允许作为实现 推导式、生成器表达式隐式作用域的一部分

  • 生成器、异步生成器参见cs_python/py3ref/dm_gfuncs

yield from

yield from:其后表达式视为子迭代器,将控制流委托给其

  • 类似管道,迭代器参数、异常都被传递给子迭代器

    • 子迭代器依次迭代结果被传递给生成器方法调用者
    • .send传递值、.throw生成异常被传递给子迭代器
  • .send传入值、.throw传入异常如果有适当方法将被传递给 下层迭代器,否则

    • sendraise AttributeErrorraise TypeError
    • throw将立即引发传入异常
  • 子迭代器完成后引发的StopIteration实例的value属性将 作为yield表达式值

    • 可以在引发StopIteration时被显式设置#todo
    • 在子迭代器是生成器时通过从子生成器返回值自动设置
  • 用途

    • 展开嵌套序列

Primaries

原型:代表编程语言中最紧密绑定的操作(优先级最高)

1
primary ::= atom | attributeref | subscription | slicing | call

Attributeref

属性引用:后面带有句点加名称的原型

1
attributeref ::= primary "." identifier
  • 要求值为支持属性引用类型的对象(多数对象支持)
  • 对象会被要求产生以指定标识符为名称的属性
    • 产生过程可以通过重载__getattr__()方法自定义

Subscriptions

抽取:在序列(字符串、元组、列表)、映射(字典)对象中选择 一项

1
subscription ::= primary "[" expression_list "]"
  • 要求值必须为支持抽取操作的对象

    • 可以定义__getitem__()方法支持抽取操作
  • 映射:表达式列表求值须为键值

    • 抽取操作选择映射中键对应值
    • 表达式列表为元组,除非其中只有一项
  • 序列:表达式列表求值须为整数、或切片

    • 正式句法规则没有要求实现对负标号值处理,但内置序列 __getitem__()方法结合序列长度解析负标号
    • 重载__getitem__的子类需要显式添加对负标号、切片 支持

Slicings

切片:在序列对象(字符串、元组、列表)中选择某个范围内的项

1
2
3
4
5
6
7
slicing      ::=  primary "[" slice_list "]"
slice_list ::= slice_item ("," slice_item)* [","]
slice_item ::= expression | proper_slice
proper_slice ::= [lower_bound] ":" [upper_bound] [ ":" [stride] ]
lower_bound ::= expression
upper_bound ::= expression
stride ::= expression
  • 可以用作表达式赋值、del语句的目标

  • 形似表达式列表的东西同样形似切片列表,所以任何抽取操作 都可以被解析为切片

    • 通过定义将此情况解析为抽取优先于切片以消除歧义
  • 原型使用__getitem__、根据切片列表构造的键进行索引

    • 切片列表包含逗号:键将为包含切片项转换的元组
    • 否则:键为单个切片项的转换
    • 切片项若为表达式:切片的转换即为切片对象

Calling

调用:附带可能为空的一系列参数来执行可调用对象

1
2
3
4
5
6
7
8
9
10
11
call                 ::=  primary "(" [argument_list [","] | comprehension] ")"
argument_list ::= positional_arguments ["," starred_and_keywords]
["," keywords_arguments]
| starred_and_keywords ["," keywords_arguments]
| keywords_arguments
positional_arguments ::= ["*"] expression ("," ["*"] expression)*
starred_and_keywords ::= ("*" expression | keyword_item)
("," "*" expression | "," keyword_item)*
keywords_arguments ::= (keyword_item | "**" expression)
("," keyword_item | "," "**" expression)*
keyword_item ::= identifier "=" expression
  • 要求值为可调用对象

    • 用户定义函数
    • 内置函数
    • 内置对象方法
    • 类对象
    • 类实例方法
    • 任何具有__call__()方法的对象
  • 调用流程

    • 参数表达式在尝试调用前被求值
    • 所有参数表达式被转换为参数列表
    • 代码块将形参绑定到对应参数表达式值
  • 除非引发异常,调用总有返回值

    • 返回值可能为None
    • 返回值计算方式取决于可调用类型
      • 用户定义函数、实例方法、类实例:函数返回值
      • 内置函数:依赖于编译器
      • 内置对象方法:类新实例
  • 在位置参数、关键字参数后加上括号不影响语义

关键字实参转位置实参

若存在关键字实参,会通过以下操作被转换为位置参数

  • 为正式参数创建未填充空位的列表
  • 若有N个位置参数:将其放入前N个空位
  • 对每个关键字参数
    • 使用标识符确定对应的空位:若标识符与第k个正式 参数名相同,使用第k个空位
    • 若空位已被填充:则raise TypeError
    • 否则将参数值放入空位进行填充
  • 所有参数处理完毕后,未填充空位使用默认值填充
  • 若仍有未填充空位,则raise TypeError;否则填充完毕 列表被作为调用的参数列表

多余实参

  • 若有关键字参数没有与之对应的正式参数名称,将 raise TypeError,除非有形参使用**indentifier句法

    • identifier将被初始化新的有序映射接收任何额外关键字 参数
    • 若没有多余关键字实参,则为相同类型空映射
  • 若位置实参数目多余位置形参数目,将raise TypeError, 除非有形参使用*identifier句法

    • identifier将初始化为元组接受任何额外位置参数
    • 没有多余位置实参,则为空元组

实参解包

  • 若实参中出现*expression句法

    • expression求值须为iterable
    • 来自该可迭代对象的元素被当作额外位置实参
    • *expression可以放在关键字实参后而没有语法错误

      • expression会优先被迭代,元素用于填充参数列表
      • 可能和关键字参数冲突,导致关键字参数对应空位被 填充
      • 一般位置实参必须位于关键字实参前,否则有语法错误
  • 若实参中出现**expresion句法

    • expression求值须为mapping
    • 其内容被当作额外关键字参数
      • 若关键字已存在,将raise TypeError

运算符

  • 运算符优先级:从低到高

    |运算符|描述| |——-|——-| |lambda|lambda表达式| |if--else|条件表达式| |or|布尔逻辑或| |and|布尔逻辑与| |not|布尔逻辑非| |innot inisis not<<=>=>!===|比较运算,包括成员检测、标识号检测| |||按位或| |^|按位异或| |&|按位与| |<<>>|移位| |+-|加、减| |*@///%|乘、矩阵乘、除、整除、取余(字符串格式化)| |+x-x~x|正、负、按位取反| |**|幂次| |await|await表达式| |x[index]x[start:end]x(arguments...)|抽取、切片、调用、属性调用| |(expression...)[expressions...]{key:value}{expressions...}|绑定或元组、列表、字典、集合显示|

  • 求值顺序:从左至右对表达式求值

    • 但赋值操作时,右侧先于左侧求值
  • 算术类型转换

    • 若任意参数为复数,另一参数转换为复数
    • 否则,若任意参数为浮点数,另一参数为浮点数
    • 否则,二者均为整数,不需要转换

await

await:挂起coroutine执行以等待awaitable对象

1
await_expr ::= "await" primary
  • 只能在协程函数中使用

幂运算符

幂运算符

1
power ::= (await_expr | primary) ["**" u_expr]
  • 优先级高于左侧一元运算符、低于右侧一元运算符

    1
    2
    3
    -1 ** 2 == -1
    0 ** 0 == 1
    # 编程语言得普遍做法
  • 语义同两个参数调用内置power函数

    • 左参数进行右参数所指定的幂次乘方运算
    • 数值参数会转换为相同类型,返回转换后类型
      • int类型做负数幂次:参数转换为float
      • 0进行负数幂次:raise ZeroDivisionError
      • 负数进行分数次幂次:返回complex类型

一元算术、位运算

一元算术、位运算

1
u_expr ::= power | "-" u_expr | "+" u_expr | "~" u_expr
  • 一元算数、位运算具有相同优先级
  • 若参数类型不正确将raise TypeError
    • +:产生数值参数相同的值
    • -:产生数值参数的负值
    • ~:只作用于整数,对整数参数按位取反,返回-(x+1) (即负值使用补码存储)

二元算术运算符

二元算术运算符

1
2
3
4
m_expr ::=  u_expr | m_expr "*" u_expr | m_expr "@" m_expr |
m_expr "//" u_expr | m_expr "/" u_expr |
m_expr "%" u_expr
a_expr ::= m_expr | a_expr "+" m_expr | a_expr "-" m_expr
  • 二元算术运算符遵循传统优先级,除幂运算符外只有两个优先 级别

    • 乘法型
    • 加法型
  • python支持混合算术,二元运算符可以用于不同类型操作数

    • 精度较低者会被扩展为另一个操作数类型

算符说明

  • @:目标是用于矩阵乘法,没有内置类型实现此运算符

  • %:模,输出第1个参数除以第2个参数的余数

    • 参数可以是浮点数
    • 结果正负总是与第2个操作数一致、或为0
    • 结果绝对值一定小于第2个操作数绝对值(数学上必然真, 但对浮点数而言由于舍入误差存在,数值上未必真)
  • //:整除,结果就是floor函数处理算术除法/的结果

    • 整除、模语义同内置函数divmod(x,y) == (x//y, x%y)
    • x接近y的整数倍,由于舍入误差的存在,x//y可能 大于(x-x%y)//y,此时python返回后一个结果,保证 divmod(x,y)[0]*y + x % y尽量接近x
  • 某些运算符也作用于特定非数字类型

    • *:两个参数分别为整数、序列,执行序列重复
    • %:被字符串对象重载,用于执行旧式字符串格式化/插值
    • +:两个参数为相同类型序列,执行序列拼接操作

移位运算

移位运算

1
shift_expr ::= a_expr | shift_expr ("<<" | ">>") a_expr
  • 优先级低于算术运算
  • 运算符接受整数参数
    • 将第一个参数左移、右移第二个参数指定的bit数
    • 右移:x >> n == x // power(2, n)
    • 左移:x << n == x * power(2, n)

二元位运算

二元位运算

1
2
3
and_expr ::=  shift_expr | and_expr "&" shift_expr
xor_expr ::= and_expr | xor_expr "^" and_expr
or_expr ::= xor_expr | or_expr "|" xor_expr
  • 三种位运算符具有不同的优先级

  • 两个参数须为整数

    • &:对两个参数进行按位AND运算
    • ^:对两个参数进行按位XOR运算
    • |:对两个参数进行按位OR运算

比较运算

比较运算

1
2
3
comparison    ::=  or_expr (comp_operator or_expr)*
comp_operator ::= "<" | ">" | "==" | ">=" | "<=" | "!="
| "is" ["not"] | ["not"] "in"
  • 所有比较运算优先级相同(与C不同)

    • 低于任何算术、移位、位运算
  • 比较运算可以任意串联a op1 b op2 c ... y opN z等价于 a op1 b and b op2 c and ... and y opN z

    • 只是后者中每个表达式最多只被求值一次

    • 例:a < b >= c类似表达式会被按照传统比较法则解读

      • 等价a < b and b >= c
      • 仍具有短路求值特性,a < b == false时,c不会 被求值

值比较

  • ><==!=<=>=比较两个对象值

    • 不要求两个对象为相同类型
    • 比较运算符实现了特定对象值概念,可以认为是通过 实现对象比较间接定义对象值
  • 所有类型继承于object,从其继承了默认比较行为

    • =!=:一致性比较,基于对象标识id

      • 具有相同标识的实例一致性比较结果相等
      • 此默认行为动机:希望对象都应该是自反射,即 x is y就意味着x == y
    • <><=>=:没有默认值提供

      • 尝试比较raise TypeError
      • 此默认行为动机:缺少一致性比较类似固定值
数字类型

数字类型:intfloatcomplex以及标准库类型 fractions.Fractiondecimal.Decimal

  • 可进行类型内部、跨类型比较

    • 类型相关限制内按数学(算法)规则正确进行比较,且不会 有精度损失
    • 复数不支持次序比较
  • 非数字值float('NaN')decimal.Decimal('NaN')

    • 同任何其他数字值比较均返回False
    • 不等于自身,但是是同一个对象(标识相同)
    • 具体实现参见cs_python/py3ref/#todo
二进制码序列

二进制码序列:bytesbytearray

  • 可以进行类型内部、跨类型比较
  • 使用元素数字值按字典序进行比较
字符串

字符串:str

  • 使用字符的Unicode码位数字值、按字典序比较
  • 字符串、二进制码序列不能直接比较
序列

序列:tuplelistrange

  • 只能进行类型内部比较

    • 跨类型:一致性比较结果为否、次序比较将 raise TypeError
    • range不支持次序比较
  • 序列元素通过相应元素进行字典序比较

    • 序列相等:相同类型、相同长度,每对相应元素必须 相等
    • 对支持次序比较序列:排序同第一个不相等元素排序,若 对应元素不同,较短序列排序较小
  • 强制规定元素自反射性:序列元素xx==x总为真

    • 即序列元素比较比较时:须首先比较元素标识,仅会对不同 元素执行==严格比较运算

    • 若序列元素为自反射元素,结果与严格比较相同;若序列 元素为非自反射元素,结果与严格比较不同

      1
      2
      3
      4
      nan = float("NaN")
      (nan is nan) == True
      (nan == nan) == False
      ([nan] == [nan]) == True
映射

映射:dict

  • 映射相等:当且进行具有相同键值对
    • 键、值一致性比较强制规定自反射性
集合

集合:setfrozenset

  • 可进行类型内部、跨类型比较

  • 比较运算符定义为子集、超集检测

    • 这类关系没有定义完全排序,如:{1,2}{2,3}集合 不相等,也没有大小比较关系
    • 所以,集合不应作为依赖完全排序的函数参数,如: minmaxsorted,将产生未定义结果
  • 集合强制规定其元素自反射性

自定比较行为
  • 其他内置类型没有实现比较方法,继承object默认比较行为
  • 可以通过实现富比较方法自定义类型的比较行为,最好 遵守一些一致性规则(不强制)
  • 自反射:相同对象比较应该相等

    • x is yx == y
  • 对称性

    • x == yy == x
    • x != yy != x
    • x < yy > x
    • x <= yy >= x
  • 可传递

    • x > y and y > zx > z
    • x < y and y <= zx < z
  • 反向比较应该导致布尔取反

    • x == ynot x != y
    • x < ynot x >= y(对完全排序)
    • x > ynot x <= y(对完全排序)
  • 相等对象应该具有相同hash值,或标记为不可hash

  • 具体实现参见cs_python/py3ref/#todo

成员检测

innot in:成员检测,后者为前者取反

  • listtuplesetfrozensetdictcollections.deque内置容器类型

    • x in yany(x is e or x == e for e in y)
    • 映射检测是否有给定键
  • 对字符串、字节串

    • 当且进当xy其子串时x in y返回True,空字符串 总被视为其他字符串子串
    • x in y等价于y.find(x) != -1
  • 自定义类型可以自定义成员检测

    • __contains__方法返回值即为x in y返回值
    • 未定义__contains__方法但定义__iter__,若迭代y 得到值z == x,则x in y == True,出现异常等同于 in引发异常
    • 具体实现参见cs_python/py3ref/#todo

标识号比较

isis not:对象标识号检测,后者为前者取反

  • 当且仅当xy为同一对象x is y == True

布尔运算

布尔运算

1
2
3
or_test  ::=  and_test | or_test "or" and_test
and_test ::= not_test | and_test "and" not_test
not_test ::= comparison | "not" not_test
  • 执行布尔运算、表达式用于流程控制语句时,以下值被解析为 假值,其余值被解析为真值

    • False
    • None
    • 所有数值类型的数值0
    • 空字符串
    • 空容器
  • andor返回最终求值参数而不是FalseTrue

    • x and y:首先对x求值

      • x求值,若为假直接返回x求值
      • 否则对y求值并返回
    • x or y:首先对x求值

      • x求值,若为真直接返回x求值
      • 否则对y求值并返回结果值
  • not必须创建新值,无论参数为和类型均范围布尔值TrueFalse

  • 可以通过自定义__bool__方法定制逻辑值

    • 具体实现参见cs_python/py3ref/#todo

条件表达式

条件表达式:三元运算符

1
2
3
conditional_expression ::=  or_test ["if" or_test "else" expression]
expression ::= conditional_expression | lambda_expr
expression_nocond ::= or_test | lambda_expr_nocond
  • 在所有python运算中具有最低优先级
  • x if C else y
    • 首先对条件C求值
    • C为真,x被求值并返回
    • 否则将对y求值并返回

lambda表达式

lambda表达式:创建匿名函数

1
2
lambda_expr        ::=  "lambda" [parameter_list] ":" expression
lambda_expr_nocond ::= "lambda" [parameter_list] ":" expression_nocond
  • lambda parameters: expression返回函数对象,同

    1
    2
    def <lambda>(parameters):
    return expression
  • lambda表达式只是简单函数定义的简单写法

表达式列表

表达式列表:除作为列表、集合显示的一部分,包含至少一个逗号 的列表表达式将生成元组

1
2
3
4
expression_list    ::=  expression ("," expression)* [","]
starred_list ::= starred_item ("," starred_item)* [","]
starred_expression ::= expression | (starred_item ",")* [starred_item]
starred_item ::= expression | "*" or_expr
  • 末尾逗号仅在创建单独元组时需要,在其他情况下可选
  • 元组长度就是列表中表达式的数量

    • 表达式将从左至右被求值
  • *表示可迭代拆包:操作数必须为iterable(同实参调用)

    • 可迭代对象将被拆解为迭代项序列,并被包含于新建的元组 、列表、集合中

Python标准库

综述

  • 内置类型相关参见cs_python/py3ref/dm_basics

常用参数说明

Functional Programming

  • key=None/callable

    • 含义:key函数,接受一个参数,返回用于排序的值
    • 默认:None,不处理
  • iterable=iterable

    • 含义:可迭代对象

数据模型--基本数据类型

对象、值、类型

对象:python中对数据的抽象

  • python中所有数据都是由对象、对象间关系表示

    • 按冯诺依曼“存储程序计算机”,代码本身也是由对象表示

编号、类型、值

每个对象都有各自编号类型

  • 编号:可以视为对象在内存中地址,对象创建后不变

    • id()函数:获取代表对象编号的整形
    • is算符:比较对象编号判断是否为同一对象
  • 类型:决定对象支持的操作、可能取值

    • 类型会影响对象行为几乎所有方面,甚至对象编号重要性 也受到影响,如:对于会得到新值的运算
      • 不可变类型:可能返回同类型、同取值现有对象引用
        • a = b = 1ab可能指向相同对象1 (取决于具体实现)
      • 可变类型:不允许返回已存在对象
        • c=[];d=[]:会保证cd指向不同、单独 空列表(c=d=[]将同一对象赋给cd
    • 对象创建后保持不变
    • type:返回对象类型
    • CPython:相同整形值都引用同一个对象
  • 值:通过一些特征行为表征的抽象概念

    • 对象值在python中是抽象概念

      • 对象值没有规范的访问方法
      • 不要求具有特定的构建方式,如:值由其全部数据 属性组成
    • 对象值可变性由其类型决定

      • 可变的:值可以改变的对象
      • 不可变的:值(直接包含对象编号)不可改变的对象
    • 比较运算符实现了特定对象值概念,可以认为是 通过实现对象比较间接定义对象值
  • CPython:id(x)返回存放x的地址

对象销毁

对象不会被显式销毁(del仅是移除名称绑定)

  • 无法访问时可能被作为垃圾回收

    • 允许具体实现推迟垃圾回收或完全省略此机制
    • 实现垃圾回收是质量问题,只要可访问对象不会被回收 即可
    • 不要依赖不可访问对象的立即终结机制,应当总是显式 关闭外部资源引用
  • 以下情况下,正常应该被回收的对象可能继续存活

    • 使用实现的跟踪、调试功能
    • 通过try...except...语句捕捉异常
  • CPython:使用带有(可选)延迟检测循环链接垃圾的 引用计数方案
    • 对象不可访问时立即回收其中大部分,但不保证 回收包含循环引用的垃圾

标准类型层级结构

  • 以下是python内置类型的列表,扩展模块可以定义更多类型
  • 以下有些类型有特殊属性,这些特殊属性不应用作通常使用, 其定义在未来可能改变

None

NoneType:只有一种取值,None是具有此值的唯一对象

  • 通过内置名称None访问
  • 多数情况表示空值,如
    • 未显式指明返回值函数返回None
  • 逻辑值:假

NotImplemented

NotImplementedType:只有一种取值,NotImplemented是具有 此值的唯一对象

  • 通过内置名称NotImplemented访问
  • 数值、富比较方法在操作数没有该实现操作时应返回此值
    • 返回NotImplemented前,解释器会依据运算符尝试反射 方法、委托回退方法
  • 逻辑值:真

Ellipsis

ellipsis:只有一种取值,Ellipsis是具有此值的唯一对象

  • 通过字面值...、内置名称Ellipsis访问
  • 逻辑值:真

numbers.Number

number.Number:由数字字面值创建,被作为算法运算符、算数 内置函数返回结果

  • 不可变:一旦创建其值不再改变
  • 类似数学中数字,但也受限于计算机对数字的表示方法

numbers.Integral

numbers.Integral:表示数学中整数集合

  • int:整形,表示任意大小数字,仅受限于可用内存

    • 变换、掩码运算中以二进制表示
    • 负数以2的补码表示(类似符号位向左延伸补满空位)
  • bool:布尔型,表示逻辑值真、假

    • TrueFalse是唯二两个布尔对象
    • 整形子类型:在各类场合中行为类似整形10,仅在 转换为字符串时返回"True""False"

方法、函数

  • int.bit_length():不包括符号位、开头0位长
  • int.to_bytes(length, byteorder, *, signed=False)
  • class int.from_bytes(bytes, byteorder, *, signed=False)

numbers.Real(float)

float:表示机器级双精度浮点数

  • 接受的取值返回、溢出处理取决于底层结构、python实现
  • python不支持单精度浮点
  • 没必要因为节省处理器、内存消耗而增加语言复杂度

特殊取值

1
2
3
4
5
infty = float("inf")
neg_infty = float("-inf")
# 正/负无穷大
nan = float("nan")
# Not a Number
  • 特殊取值根据定义==is肯定返回False

    • float.__eq__内部应该有做检查,保证==返回False
    • 每次会创建“新”的nan/infty
    • 连续执行id(float("nan"))返回值可能相等,这是因为 每次生成的float("nan")对象被回收,不影响
  • np.nan is np.nan返回True,应该是numpy初始化的时候 创建了一个float("nan"),每次都是使用同一个nan

相关操作

  • float.as_integer_ratio()
  • float.is_integer()
  • float.hex()
  • classmethod float.fromhex(s)
  • round(f[,n])
  • math.trunc(f)
  • math.floor(f)
  • math.ceil(f)

numbers.Complex(complex)

complex:以一对机器级双精度浮点数表示复数值

  • 实部、虚部:可通过只读属性z.realz.imag获取

Iterators

迭代器类型

  • 迭代器对象需要自身支持以下两个方法,其共同组成迭代器协议
    • iterator.__iter__()
    • iterator.__next__()
  • 方法详细参考cs_python/py3ref/cls_special_method

Generator

生成器类型:提供了实现迭代器协议的便捷形式

  • 将容器对象的__iter__()方法实现为生成器,方便实现容器对 迭代器支持
  • 创建、使用参见cs_python/py3ref/dm_gfuncs

序列

序列:表示以非负整数作为索引的有限有序集

  • 不可变序列类型:对象一旦创建不能改变

    • 若包含其他可变对象引用,则可变对象“可改变”
    • 但不可变对象所直接引用的对象集是不可变的
    • 包括
      • str
      • tuple
      • bytes
      • range:非基本序列类型
  • 可变序列:创建后仍可被改变值

    • list
    • bytesarray

通用序列操作

  • x in sx not in s

    • strbytesbytearray支持子序列检测
  • s + t:拼接

    • 拼接不可变总会生成新对象
    • 重复拼接构建序列的运行时开销将基于序列总长度乘方
  • s * nn * ss自身拼接n

    • n<0被当作0处理
    • s中项不会被复制,而是被多次引用
  • s[i]s[i:j]s[i:j:step]

    • i<0索引为负值:索引顺序相对于序列s末尾,等价于 对序列长度取模
    • 序列切片:与序列类型相同的新序列
      • 索引从0开始
      • 左闭右开
    • 某些序列支持a[i:j:step]扩展切片
  • s.index(x[, i[, j]])

    • 仅部分序列支持
    • 类似s[i:j].index(x),但返回值是相对序列开头
  • s.count(x):序列中元素x数目

  • len(s):返回序列条目数量

  • min(s)max(s):序列最小、最大值

  • 序列比较运算默认实现参见cs_python/py3ref/expressions
  • 以上运算自定义实现参见 cs_python/py3ref/cls_special_methods

不可变序列

不可变序列普遍实现而可变序列未实现的操作

  • hash()内置函数

可变序列

  • s[i]=xs[i:j]=ts[i:j:k]=t:下标、切片被赋值
    • s[i:j:k]=tt长度必须和被替换切片长度相同
  • del s[i:j]del s[i:j:k]:移除元素
    • 作为del语句的目标
    • 等同于s[i:j]=[]
  • s.append():添加元素
    • 等同于s[len(s):len(s)] = [x]
  • s.clear():移除所有项
    • 等同于del s[:]
  • s.copy():浅拷贝
    • 等同于s[:]
  • s.extend(t):扩展(合并)序列
    • 基本上等于s += t
  • s.insert(i, x):向序列中插入元素
    • 等同于s[i:i] = [x]
  • s.pop(i=-1):弹出序列中元素
  • s.remove(x):删除序列中首个值为x的项
  • s.reverse():反转序列
    • 反转大尺寸序列时,会原地修改序列
    • 为提醒用户此操作通过间接影响进行,不会返回反转后序列
  • arraycollections模块提供额外可变序列类型
  • 可利用collections.abc.MutableSequence抽象类简化自定义 序列操作

tuple

元组

  • 元组中条目可以是任意python对象
  • 元组创建
    • 一对圆括号创建空元组
    • 逗号分隔
      • 单项元组:后缀逗号a,(a,)
      • 多项元组:a,b,c(a,b,c)
    • 内置构建器:tupletuple(iterable)

list

列表

  • 列表中条目可以是任意python对象
  • 构建方式
    • 方括号括起、项以逗号分隔:[][a][a,b]
    • 列表推导式:[x for x in iterable]
    • 类型构造器:list(iterable)

相关操作

.sort
1
2
def list.sort(*, key=None, reverse=False):
pass
  • 用途:对列表原地排序

    • 使用<进行各项之间比较
    • 不屏蔽异常:若有比较操作失败,整个排序操作将失败, 此时列表可能处于部分被修改状态
  • 参数

    • key:带参数函数,遍历处理每个元素提取比较键
      • None:默认,直接使用列表项排序
  • 说明

    • .sort保序,有利于多重排序
    • 为提醒用户此方法原地修改序列保证空间经济性,其不返回 排序后序列(可考虑使用sorted显式请求)
  • CPython:列表排序期间尝试改变、检测会造成未定义影响, CPython将列表排序期间显式为空,若列表排序期间被改变将 raise ValueError

str

1
2
3
4
5
class str(object="")
# 返回`object.__str__()`、`object.__repr__()`
class str(object=b"", encoding="utf-8", errors="strict")
# 给出`encoding`、`errors`之一,须为bytes-like对象
# 等价于`bytes.decode(encoding, errors)`

字符串:由Unicode码位值组成不可变序列(应该是UTF16-bl编码)

  • 范围在U+0000~U+10FFFF内所有码位值均可在字符串中使用
  • 不存在单个“字符”类型
    • 字符串中单个字符为长度为1字符串
  • 不存在可变字符串类型
    • 可以用str.join()io.StringIO高效连接多个字符串 片段
  • 字符串构建
    • 字符串字面值:cs_python/py3ref/lexical_analysis
    • 内置构造器str()

相关操作

  • ord():转换单个字符字符串为(整形)码位
  • chr():转换(整形)码位为单个字符字符串
判断
  • str.isalnum()
  • str.isalpha()
  • str.isascii()
  • str.isdecimal()
  • str.isdigit()
  • str.isidentifier()
  • str.islower()
  • str.isnumeric()
  • str.isprintable()
  • str.isspace()
  • str.istitle()
  • str.isupper()
查找
  • str.rfind(sub[, start[, end]])
  • str.rindex(sub[, start[, end]])
  • str.startswith(prefix[, start[, end]])
  • str.endwith(suffix[, start[, end]])
  • str.count(sub[, start[, end]]):子串出现次数
  • str.find(sub[, start[, end]])
    • 仅检查sub是否为子串,应使用in
    • 找不到子串时返回-1
  • str.index(sub[, start[, end]])
    • 类似str.find,但找不到子串时raise ValueError
分隔
  • str.partition(sep)
  • str.rpartition(sep)
  • str.rsplit(sep=None, maxsplit=-11)
  • str.split(sep=None, maxsplit=-1)
  • str.splitline([keepends])
拼接
  • str.join(iterable)
  • str.strip([chars])
  • str.lstrip([chars])
  • str.rstrip([chars])
  • str.rstrip([chars])
转换
  • str.lower()
  • str.upper()
  • str.swapcase()
  • str.translate(table)
  • str.replace(old, new[, count])
  • static str.maketrans(x[, y[, z]])
  • str.encode(encoding="utf-8", errors="strict"):使用 指定编码方案编码为bytes
  • str.expandtabs(tabsize=8)
  • str.capitalize():首字符大写副本
  • str.casefold():消除大小写副本
  • str.center(width[, fillchar]):字符串位于中间的字符串
  • str.title()
格式化
  • str.ljust(width[, fillchar])
  • str.rjust(width[, fillchar])
  • str.zfill(width)
  • str.format(*args, **kwargs)
  • str.format_map(mapping)
    • 类似str.format(**mapping),但mapping不会被复制 到dict
      1
      2
      3
      4
      class Default(dict):
      def __missing__(self, key):
      return key
      "{name} was born in {country}".format_map(Default(name="Guido"))
printf风格字符串格式化
  • format % values中:format%转换标记符将被转换 为values中条目

    • 效果类似于sprintf
    • values为与format中指定转换符数量等长元组、或映射 对象,除非format要求单个参数
  • 转换标记符按以下顺序构成

    • %字符:标记转换符起始
    • 映射键:可选,圆括号()括起字符序列
      • values为映射时,映射键必须
    • 转换旗标:可选,影响某些类型转换效果
      • #:值转换使用“替代形式”
      • 0:为数字值填充0字符
      • -:转换值左对齐(覆盖0
      • :符号位转换产生整数(空字符串)将留出空格
      • +:符号字符显示在开头(覆盖
    • 最小字段宽度:可选
      • *:从values读取下个元素
    • 精度:可选,.之后加精度值
      • *:从values读取下个元素
    • 长度修饰符:可选
    • 转换类型
      • d/u/i:十进制整形
      • o:8进制整形
        • #替代形式,前端添加0o
      • x/X:小/大写16进制整形
        • #替代形式,前端添加0x/0X
      • e/E:小/大写浮点指数
        • #替代形式,总是包含小数点
      • f/`F:浮点10进制
        • #替代形式,总是包含小数点
      • g/G:指数小于-4、不小于精度使用指数格式
        • #替代形式,总是包含小数点,末尾0不移除
      • c:单个字符(接收整数、单个字符字符串)
      • r/s/a:字符串(repr/str/ascii转换)
        • 按输出精度截断
      • %:输出%字符

技巧

  • 快速字符串拼接
    • 构建包含字符串的列表,利用str.join()方法
    • 写入io.StringIO实例,结束时获取值

bytes/bytearray

1
class bytes([source[, encoding[, errors]]])
  • 字节串:单个字节构成的不可变序列
  • 字节数组:字节串可变对应版本,其他同不可变bytes
  • 字节串构建

    • 字节串字面值:cs_python/py3ref/lexical_analysis
    • 内置构造器bytes()
      • 指定长度零值填充:bytes(10)
      • 整数组成可迭代对象:bytes(range(20))
      • 通过缓冲区协议复制现有二进制数据:bytes(obj)
  • 字节数组构建

    • 字节数组没有字面值语法,只能通过构造器构造
    • 可变,构建空字节数组有意义
  • 类似整数构成序列

    • 每个条目都是8位字节
    • 取值范围0~255,但只允许ASCII字符0~127
    • b[0]产生整数,切片返回bytes对象
    • 可通过list(bytes)bytes对象转换为整数构成列表
  • memeoryview提供支持

相关函数、方法

  • bytes.decode:解码为相关字符串
  • classmethod bytes.fromhex(string)
  • bytes.hex()

技巧

  • 快速字节串拼接
    • 构建包含字节串的列表,利用bytes.join()方法
    • 写入io.BytesIO实例,结束时获取值
    • 使用betaarray对象进行原地拼接

memoryview

1
class memoryview(obj)

内存视图:允许python代码访问对象内部数据

  • 若对象支持缓冲区协议,则无需拷贝

    • 支持缓冲区协议的内置对象包括bytesbytesarray
  • 内存视图元素:原始对象obj处理的基本内存单元

    • 对简单bytesbytesarray对象,一个元素就是一字节
    • array.array等类型可能有更大元素
  • 内存视图支持索引抽取、切片

    • 若下层对象可选,则支持赋值,但切片赋值不允许改变大小

相关操作

  • mv.__eq__(exporter)
  • mv.__len__()
  • mv.tobyte()
  • mv.hex()
  • mv.tolist()
  • mv.release()
  • mv.cast(format[, shape]):将内存视图转换新格式形状

可用属性

以下属性均只读

  • mv.obj:内存视图的下层对象
  • mv.nbytes
    • == product(shape) * itemsize = len(mv.tobytes())
  • mv.readonly
  • mv.format:内存视图中元素格式
    • 表示为struct模块格式
  • mv.itemsize
  • mv.ndim
  • mv.shape
  • mv.strides
  • mv.suboffsets
  • mv.c_contiguous
  • mv.f_contiguous
  • mv.contiguous

Slices Object

切片对象:表示__getitem__()方法得到的切片

  • 可以使用内置的slice()函数创建
  • a[start: stop]形式的调用被转换为 a[slice(start, stop, None)]
  • 切片对象是内部类型,参见cs_python/py3ref/dm_exec,也 不是序列类型

特殊只读属性

  • start:下界
  • stop:上界
  • step:步长值
  • 属性可以具有任意类型

方法

  • .indices(self, length):计算切片对象被应用到length 长度序列时切片相关信息
    • 返回值:(start, stop, step)三元组
    • 索引号缺失、越界按照正规连续切片方式处理

range

range:不可变数字序列类型(非不是基本序列类型)

1
2
class range(stop)
class range(start=0, stop[, step=1])
  • 参数:必须均为整数(int或实现__index__方法)

    • step > 0:对range对象r[i]=start + step * i,其中 i >= 0, r[i] < stop
    • step < 0:对range对象r[i]=start + step * i,其中 i >= 0, r[i] > stop
    • step = 0raise ValueError
  • 说明

    • 允许元素绝对值大于sys.maxsize,但是某些特性如: len()可能raise OverflowError
    • range类型根据需要计算单项、切片值
      • 相较于常规listtuple占用内存较小,且和表示 范围大小无关
      • 只能表示符合严格模式的序列
    • range类型实现了collections.abc.Sequence抽象类
      • 基本实现序列所有操作:检测、索引查找、切片等
      • 除拼接、重复:拼接、重复通常会违反严格模式
    • !===range对象视为序列比较,即提供相同值即 认为相等

集合类型

  • 表示不重复不可变对象组成的无序、有限集合

    • 不能通过下标索引
    • 可以迭代
    • 可以通过内置函数len返回集合中条目数量
  • 常用于

    • 快速成员检测、去除序列中重复项
    • 进行交、并、差、对称差等数学运算

公用操作

  • len(s)
  • x [not ]in s
  • s.isdisjoint(other)
  • s.issubset(other)/s <= other
  • s < other
  • s.issuperset(other)/s >= other
  • s > other
  • s.union(*others)/s | other |...
  • s.intersection(*others)/s & other &...
  • s.difference(*other)/s - other - other
  • s.symmetric_difference(other)/s ^ other
  • s.copy()
  • 集合比较仅定义偏序,集合列表排序无意义

可变集合独有

  • s.update(*others)/s |= other |...
  • s.intersection_update(*others)/s &= other &...
  • s.difference_udpate(*others)/s -= other |...
  • s.symmetric_difference_update(other)/set ^= other
  • s.add(elem)
  • s.remove(elem)
  • s.discard(elem)
  • s.pop()
  • s.clear()

set/frozenset

1
2
class set([iterable])
class frozenset([iterable])
  • 集合:由具有唯一性的hashable对象组成的多项无序集
  • 冻结集合:不可变集合,可哈希,可以用作集合元素、字典键
  • 创建集合

    • set()内置构造器
    • 花括号包括、逗号分隔元组列表:{a, b}
  • 创建冻结集合

    • frozenset()内置构造器
  • python中集合类似dict通过hash实现

    • 集合元素须遵循同字典键的不可变规则
    • 数字:相等的数字1==1.0,同一集合中只能包含一个

操作说明

  • .remove.__contains__discard等可以接收set类型 参数,其将被转换为临时frozenset对象

  • 非运算符版本操作可以接受任意可迭代对象作为参数,运算符 版本只能接受集合类型作为参数

映射

映射:表示任何索引集合所索引的对象的集合

  • 通过下标a[k]可在映射a中选择索引为k的条目
    • 可在表达式中使用
    • 可以作为赋值语句、del语句的目标
  • dbm.ndbmdbm.gnucollections模块提供额外映射类型

通用操作

  • len(d)
  • d[key]
  • key [not ]in d
  • iter(d)
  • d.keys():返回字典视图对象
  • d.values():返回字典视图对象
  • d.items():返回字典视图对象
  • d.get(key[, default])
  • d.copy()
  • classmethod fromkey(iterable[, value])

可变映射独有

  • d[key]=value
  • del d[key]
  • d.clear()
  • d.setdefault(key[, default])
  • d.pop()
  • d.popitem()
  • d.copy()
  • d.update()

dict

1
2
3
class dict(**kwargs)
class dict(mapping, **kwargs)
class dict(iterable, **kwargs)

字典:可由几乎任意值作为索引的有限个对象可变集合

  • 字典的高效实现要求使用键hash值以保持一致性

    • 不可作为键的值类型
      • 包含列表、字典的值
      • 其他通过对象编号而不是值比较的可变对象
    • 数字:相等的数字1==1.0索引相同字典条目
  • 创建字典

    • 花括号括起、逗号分隔键值对:{key:value,}
    • 内置字典构造器:dict()

字典视图对象

字典视图对象:提供字典条目的动态视图,随字典改变而改变

  • len(dictview)
  • iter(dictview)
  • x in dictview

Special Methods

综述

特殊方法:python类中具有特殊名称的方法,实现由特殊语法 所引发的特定操作

  • python实现操作符重载的方式

    • 允许每个类自行定义基于操作符的特定行为
    • 特定操作包括python内置的钩子函数
  • 钩子函数不能简单的看作直接调用特殊方法

    • 尝试调用备用实现:iterreversed
    • 修改方法返回值:dir
  • 大部分情况下,若没有定义适当方法,尝试执行操作raise AttributeErrorraise TypeError

    • __hash____iter____reversed____contains__等方法即使未定义,其对应钩子函数 实现会尝试调用可能的其他方法完成操作 (直接obj.__xxx__调用方法仍然报错)

    • 将特殊方法设为None表示对应操作不可用,此时即使以上 hashiterreversedin等操作也不会尝试调用 备用方法

实例创建、销毁

调用类时,元属性方法执行顺序

  • __prepare__():创建命名空间
  • 依次执行类定义语句
  • __new__():创建类实例
  • __init__():初始化类
    • __new__返回的新实例__init__方法将被调用
    • 用户定义__new__返回对象不一定期望类实例,调用的 __init__随之不一定是期望方法
  • 返回__new__返回类实例

__prepare__

  • 在所有类定义开始执行前被调用,用于创建类命名空间
  • 一般这个方法只是简单的返回一个字典或其他映射对象

__new__

1
2
classmethod object.__new__(cls[, *args, **kwargs]):
pass
  • 用途:创建、返回cls类新实例

    • super().__new__(cls[,...])调用超类方法创建类实例, 然后根据需要修改新创建实例再返回
  • 参数

    • cls:待实例化类
    • 其余参数:类构造器表达式参数
  • 返回值:cls类新实例

    • __new__返回值就是类构造器的返回值,有绝对控制权

说明

  • __new__builtin_function_or_method

  • __new__是静态方法:以需实例化类作为第一个参数

    • __new__方法绑定当前类对象
    • 特例,不需要显式声明为静态方法
  • 原生有两个__new__函数,二者C实现不同

    • type.__new__:元类继承,用于创建类对象
    • object.__new__:其他类继承,用于创建实例

__init__

1
2
def object.__init__(self[, *args, *kwargs]):
pass
  • 用途:初始化类实例

    • 类构造器中__new__返回类实例调用此方法初始化
    • 若基类有用户定义__init__方法,则其派生类__init__ 应该显式调用基类__init__保证基类部分正确初始化
  • 参数

    • self:当前类实例
    • 其余参数:类构造器表达式参数
  • 返回值:None,否则raise TypeError

__del__

1
def object.__del__(self)
  • 用途:实例销毁时(引用计数变为0)被调用
    • 若基类有__del__方法,则其派生类__del__方法中 需要显式调用基类__del__保证基类部分正确清除
    • 对象重生:在其中创建该实例的新引用推迟其销毁
      • 不推荐
      • 重生对象被销毁时__del__是否会被再次调用取决于 具体实现
      • 当前CPython实现中只会调用一次

说明

  • 解释器退出时不会确保为仍然存在的对象调用__del__方法
  • “钩子函数”:del
    • del x不直接调用x.__del__()
    • del x仅将x的引用计数减一

输出属性

__repr__

1
2
def object.__repr__(self):
pass
  • 用途:输出对象的“官方”字符串表示

    • 如果可能,应类似有效的python表达式,可以用于重建具有 相同取值的对象(适当环境下)
    • 若不可能,返回形如<...some useful description...> 的字符串
    • 常用于调试,确保内容丰富、信息无歧义很重要
  • 返回值:字符对象

    • 内置钩子函数:repr
    • 交互环境下直接“执行”变量的结果

__str__

1
2
def object.__str__(self):
pass
  • 用途:生成对象“非正式”、格式良好的字符串表示

    • 返回较方便、准确的描述信息
  • 返回值:字符串对象

    • 内置钩子函数:str

说明

  • object.__str__方法默认实现调用object.__repr__

    • 所以若未定义__str__,需要实例“非正式”字符串表示时 也会使用__repr__
  • formatprint函数会隐式调用对象__str__方法

    • 此时若__str__返回非字符串会raise TypeError

__bytes__

1
2
def object.__bytes__(self):
pass
  • 用途:生成对象的字节串表示

  • 返回值:bytes对象

    • 内置钩子函数:bytes

__format__

1
def object.__format__(self, format_spec)
  • 用途:生成对象的“格式化”字符串表示

    • 内部常调用formatstr.format实现格式化
    • object.__format__(x, '')等同于str(x)
  • 参数

    • fomrat_spec:包含所需格式选项描述的字符串
      • 参数解读由实现__format__的类型决定
      • 大多数类将格式化委托给内置类型、或使用相似格式化 语法
  • 返回值:字符串对象

    • 内置钩子函数:format

__hash__

1
2
def object.__hash__(self):
pass
  • 用途:计算对象hash值返回

    • 相等的对象(即使类型不同)理应具有相同hash值
    • 建议把参与比较的对象的全部组件的hash值打包为元组, 对元组做hash运算
      1
      2
      def __hash__(self):
      return hash((self.name, self.nick, self.color))
  • 返回值:整数

    • 内置钩子函数:hash()

说明

  • hash()会从对象自定义的__hash__()方法返回值中截断为 Py_ssize_t大小

    • 64bits编译平台通常为8bytes、32bits为4bytes
    • 若对象__hash__()需要在不同位大小的平台上互操作, 需要检查支持的平台位宽
    • 查看sys.hash_info.width
  • setfrozensetdict这3个hash集类型中成员的操作 会调用相应__hash__()

  • 类的__hash__方法设置为None

    • 尝试获取实例hash值时将raise TypeError
    • isinstance(obj, collecitons.abc.Hashable)返回 False
    • 单纯在__hash__中显式raise TypeError会被错误 认为是可hash

关联__eq__

hash绝大部分应用场景是比较是否相等,所以__hash____eq__ 密切相关

  • 类未定义__eq__

    • 也不应该定义__hash__,单独hash结果无法保证比较结果
  • 类实现__eq__

    • 未定义__hash__:其实例将不可被用作hash集类型的项
    • 类中定义了可变对象:不应该实现__hash__,因为hash集 实现要求键hash值不可变
  • 类重载__eq__方法

    • 默认其__hash__被隐式设为None
    • 否则须设置__has__ = <ParentClass>.__hash__显式保留 来自父类__hash__实现

默认实现

  • floatintegerdecimal.Decimal等数字类型hash运算 是基于为任意有理数定义的统一数学函数

  • strbytesdatetime对象__hash__值会使用不可预知 值随机加盐

    • 盐在单独python进程中保持不变,但在重复执行的python 进程之间是不可预测的
    • 目的是为了防止某种形式的DDos服务攻击
  • 改变hash值会影响集合迭代次序

    • python也不保证次序不会改变

__bool__

1
2
def object.__bool__(self):
pass
  • 用途:返回TrueFalse实现真值检测

    • 未定义:调用__len__返回非0值时对象逻辑为真
    • __len____bool__均未定义:所有实例逻辑为真
  • 返回值:FalseTrue

    • 内置构造函数:bool()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Pair:
def __init__(self, x, y):
self.x = x
self.y = y

def __repr__(self):
# 返回实例代码表示形式
# 通常用于重新构造实例
return "Pair({0.x!r}, {0.y!r})".format(self)
# 格式化代码`!r`指明输出使用`__repr__`而不是默认
# 的`__str___`
# 格式化代码`0.x`表示第一个参数`x`属性

def __str__(self):
return "({0.x!s}, {0.y!s})".format(self)
# 格式化代码`!s`指明使用默认`__str__`

def __format__(self):
if self.x == 0:
return self.y
elif self.y == 0:
return self.x
return "{0.x!r}, {0.y!r}".format(self)

Rich Comparison Methods

富比较方法

1
2
3
4
5
6
7
8
9
10
11
12
def object.__lt__(self, other):
pass
def object.__le__(self, other):
pass
def object.__eq__(self, other):
pass
def object.__ne__(self, other):
pass
def object.__gt__(self, other):
pass
def object.__ge__(self, other):
pass
  • 用途:比较运算符重载

    • x < y:调用x.__lt__(y)
    • x <= y:调用x.__le__(y)
    • x == y:调用x.__eq__(y)
    • x != y:调用x.__ne__(y)
  • 返回值

    • 成功比较返回FalseTrue
    • 若指定方法没有相应实现,富比较方法会返回单例对象 NotImplemented
  • 比较运算默认实现参见cs_python/py3ref/expressions

说明

  • 默认情况下,__ne__会委托给__eq__,并将结果取反,除非 结果为NotImplemented

  • 比较运算符之间没有其他隐含关系

    • x < y or x == y为真不意味着x <= y
    • 要根据单个运算自动生成排序操作可以利用 functools.total_ordering()装饰器简化实现
  • 以上方法没有对调参数版本(左边参数不支持该操作,右边参数 支持该操作)

    • 若两个操作数类型不同、且右操作数是左操作数直接或间接 子类,优先选择右操作数的反射方法,否则左操作数 方法(不考虑虚拟子类)
    • 反射方法
      • __lt____gt__互为反射
      • __le____ge__互为反射
      • __eq____ne__各为自身反射

内部信息

__dict__

  • 钩子函数:varsdir(部分)

    • vars是真正对应的钩子函数,返回键值对
    • dir执行过程中会访问__dict____class__,而且 只返回keys
  • 对象底层字典,存储对象属性、方法

    • 注意区分开:实例属性、类属性、基类属性,__dict__ 只包括当前实例属性、方法
    • 返回结果是dir结果的子集
  • 调用实例obj的属性时,按照以下顺序查找

    • obj.__dict__:当前实例的__dict__
    • type(obj).__dict__:实例所属类的__dict__
    • type(obj).mro().__dict__:基类的__dict__
  • 在大部分情况下__dict__会自动更新,如setattr函数时, 或说实例的属性、方法更新就是__dict__的变动

    • 一般情况下不要直接访问__dict__,除非真的清楚所有 细节,如果类使用了cls.__slots__@property、 描述器类等高级技术时代码可能会被破坏

    • 尽量使用setattr函数,让python控制其更新

__class__

  • 用途:返回实例所属类

  • 返回值:实例(狭义)返回类、类返回元类

    • 钩子函数:type

__objclass__

  • 用途:被inspect模块解读为指定实例所在的类
    • 合适的设置可以有助于动态类属性的运行时检查
    • 对于可调用对象:指明第一个位置参数应为特定类型的 实例、子类
      • 描述器类:instance参数

        todo

__slots__

  • 用途:显式声明数据成员、特征属性,限制实例添加属性

    • 可赋值为:字符串、可迭代对象、实例使用的变量名构成的 字符串序列

      • 可迭代对象中元素可以是任何类型
      • 还可以映射类型,未来可能会分别赋给每个键特殊含义 的值
    • __slots__会为已声明变量保留空间

      • 直接访问将raise AttributeError
      • dir可以找到__slots__中声明的变量
  • 阻止默认为每个实例创建__dict____weakref__的 行为,除非在__slots__中显式声明、或在父类中可用

    • __dict__属性实例无法给未在__slots__中列出 的新变量赋值

      • 但是python很多特性依赖于普通的依赖字典实现,定义 __slots__的类不再支持普通类某些特性
      • 大多数情况下,应该只在经常使用到作为数据结构的 类上定义__slots__
      • 不应该把__slots__作为防止用户给实例增加新属性 的封装工具
    • __weakref__属性实例不支持对实例的弱引用

  • 是阻止给实例创建__dict__,类本身仍然有__dict__属性 (dir返回值中无__dict____dir__返回值中有)

说明

  • __slots__声明的行为不只限于定义其的类

    • 父类中声明__slots__可以在子类中使用,但子类将获得 __dict____weakref__,除非其也定义了__slots__

    • 子类__slots__中定义的slot将覆盖父类中同名slot

      • 需要直接从基类直接获取描述器才能访问
      • 这会导致程序未定义,以后增加检查避免
    • 多重继承中只允许一个父类具有非空__slots__,否则 raise TypeError

  • __slots__是在类层次上的实现:为每个变量创建描述器

    • 类属性不能被用于给在__slots__中定义变量设置默认值
    • 否则类属性会覆盖描述器赋值,变成只读属性
  • 非空的__slots__不适用于派生自“可变长度”内置类型,如 intbytestuple

  • 定义类属性__slots__后,python会为实例属性使用紧凑内部 表示

    • 实例属性使用固定大小、很小的数组构建,而不是为每个 实例定义字典
    • __slots__列出的属性名在内部映射到数组指定下标上
    • 类似于R中factor类型、C中enum类型
    • 相比__dict__可以显著节省空间、提升属性查找速度
1
2
3
4
5
6
class Date:
__slots__ = ["year", "month", "day"]
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
  • 继承自未定义__slots__类时,实例中__dict____weakref__属性将总是可访问的

  • __class__赋值仅在两个类具有相同__slots__值时才有用

自定义属性访问

__getattr__

1
2
def object.__getattr__(self, name):
pass
  • 用途:.默认属性访问引发AttributeError而失败时调用

    • 如果属性通过正常机制找到,__getattr__不会被调用
      • __getattr____setattr__之间故意设置的 不对称性
      • 出于效率考虑
    • 对实例变量而言,无需在实例属性字典中插入值,就可以 模拟对其的完全控制
  • 返回值:计算后的属性值、或raise AttributeError

说明

  • 可能引发AttributeError

    • 调用__getattribute__时因为name不是实例属性、 或是类关系树中属性
    • 对调用__get__获取name描述器
  • 调用__getattr__.运算符中逻辑

    • __getattribute__显式调用raise AtttributeError 不会调用__getattr__
  • __getattr__甚至不是object具有的 <wrapper_descriptor>

  • 相较于__getattribute__其实更常用,因为修改所有对 对对象的访问逻辑没啥价值

__getattribute__

1
2
3
4
5
6
def __getattribute__(self, key):
"Emulate type_getattro() in Objects/typeobject.c"
v = object.__getattribute__(self, key)
if hasattr(v, "__get__"):
return v.__get__(None, self)
return v
  • 用途:访问对象属性时无条件被调用

    • 判断访问属性类型、做对应操作
      • 描述器:调用描述器方法
      • 实例方法:为类中函数绑定实例
      • 类方法:为类中函数绑定类
      • 静态方法:不绑定
      • 普通属性
    • 作为通过特定语法、内置函数隐式调用的结果情况下, 查找特殊方法时仍可能被跳过
  • 返回值:找到的属性值、或raise AttributeError

  • __getattribute__仅对继承自object的新式类实例可用

说明

  • 内置类型均有各自__getattribute__函数实例

    • 其均为wrapper_descriptor类型(C实现的函数)
    • 各函数实例标识符不同,若其均“继承自object”,其 应为同一个函数实例
    • 自定义类真继承自object类,其__getattribute__object.__getattribute__
  • 自定义实现

    • 避免方法中无限递归,实现总应该调用具有相同名称 基类方法访问所需要的属性

钩子函数

  • .运算符:首先调用__getattribute__,若无访问结果, 调用__getattr__

    • .运算符说明参见cs_python/py3ref/cls_basics
  • getattr:基本同.运算符,除可捕获异常,设置默认返回值

  • hasattr:内部调用getattr,根据raise Exception判断 属性是否存在

    • 可以通过@property.getterraise AttributeError 使得属性看起来不存在
    • 内部有更多boilerplate相较于getattr更慢
    • 则按照字面意思使用不需要考虑过多

__setattr__

1
2
def object.__setattr__(self, name, value):
pass
  • 用途:属性被尝试赋值时被调用

    • 默认实现:将值保存到实例字典
    • __setattr__要赋值给实例属性,应该调用同名基类 方法
  • 返回指:None

    • 钩子函数:setattr

__delattr__

1
2
def object.__delattr__(self, name):
pass
  • 用途:删除实例属性时被调用

    • 默认实现:从实例字典中删除对应项
    • 应该在del obj.name对该对象有意义时才实现
  • 返回值:None

    • 内置钩子函数:delattrdel

__dir__

1
2
def object.__dir__(self):
pass
  • 用途:返回实例中“可访问”名称的字符串列表

    • 默认实现:返回实例、类、祖先类所有属性
    • 交互式解释器就是在__dir__/dir返回列表中进行查询 进行补全
  • 返回值:序列

    • 内置钩子函数:dir
      • dir()获取__dir__返回序列,转换为列表、排序
      • dir()会剔除__dir__返回值中部分值
      • __dir__返回值不可迭代,报错

自定义模块属性访问

  • __getattr____dir__可以用于自定义对模块属性的访问

    • 模块层次__getattr__类似普通类
      • 接受属性名作为参数
      • 返回计算后结果、或raise AttributeError
      • 若正常查找__getattribute__无法在模块中找到某个 属性,调用__getattr__
    • 模块层次__dir__类似普通类
      • 不接受参数
      • 返回模块中可访问名称的字符串列表
  • 可以将模块的__class__属性设置为types.ModuleType子类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    import sys
    import types import ModuleType

    class VersboseModule(ModuleType):
    def __repr__(self):
    return f"verbose {self.__name__}"
    def __setattr__(self, attr, value):
    print(f"settting {attr}")
    super().__setattr__(attr, value)

    sys.modules[__name__].__class__ = VerboseModule
  • 设置模块__getattr____class__只影响使用属性访问 语法进行查找,直接访问模块全局变量(通过模块内代码、对 模块全局字典引用)不受影响

描述器类

描述器:具有“绑定行为”的对象属性

  • 类中定义其中任意一个方法,则其实例被称为描述器

    • __set__
    • __get__
    • __delete__
  • 所有对描述器属性的访问会被__get____set____delete__方法捕获/重载

    • 如果只是想简单的自定义某个类的属性处理逻辑,使用 @porperty装饰器简化实现
  • @property参见cs_python/py3ref/cls_basics

描述器协议

  • 以下方法仅包含其的类的实例出现在类属性中才有效
    • 即以下方法必须在(祖先)类__dict__中出现,而不是 实例__dict__
    • 即描述器只能定义为类属性,不能定义为实例属性

__get__

1
2
def object.__get__(self, instance, owner=None):
pass
  • 用途:访问描述器属性时调用,重载实例属性访问

    • 若描述器未定义__get__,则访问属性会返回描述器对象 自身,除非实例字典__dict__中有同名属性
    • 若仅仅只是从底层实例字典中获取属性值,__get__方法 不用实现
  • 参数

    • instance:用于方法属性的实例
    • owner:实例所属类,若通过类获取属性则为None
  • 返回值:计算后属性值、或raise AttributeError

  • 示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    def __get__(self, instance, cls):
    if instance is None:
    # 装饰器类一般作为类属性,需要考虑通过类直接访问
    # 描述器类属性,此时`instance is None`
    # 常用操作是返回当前实例
    return self
    else:
    return instance.__dict__[self.name]

    # self:描述器类当前实例
    # instance:定义描述器作为类属性的类的实例
    # cls:定义描述器作为类属性的类

__set__

1
2
def object.__set__(self, instance, name, value):
pass
  • 用途:设置实例instance的“描述器属性”值为value,重载 实例属性赋值

    • 常用实现:操作实例instance.__dict__存储值,使得 看起来是设置普通实例属性
  • 示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    def __set__(self, instance, name, value):
    if instance is None:
    pass
    else:
    if not instance(value, int):
    raise TypeError("expect an int")
    instance.__dict__[self.name] = value
    # 操作实例底层`__dict__`

    # `value`:赋给描述器类属性的值

__delete__

1
2
def object.__delete__(self, instance):
pass
  • 用于:“删除”实例instance的“描述器属性”,重载实例属性 删除

    • 具体实现应取决于__set__实现
  • 示例

    1
    2
    3
    4
    5
    6
    def __delete__(self, instance):
    if instance is None:
    pass
    else:
    del instance.__dict__[self.name]
    # 操作实例底层`__dict__`

__set_name__

1
2
def object.__set_name__(self, owner, name):
pass
  • 用途:类owner被创建时调用,描述器被赋给name

实现原理

  • 描述器的实现依赖于object.__getattribute__()方法

    • 可以通过重写类的__getattribute__方法改变、关闭 描述器行为
  • 描述器调用:描述器x定义在类A中、a = A()

    • 直接调用:x.__get__(a)
    • 实例绑定:a.x
      • 转换为:type(a).__dict__['x'].__get__(a)
    • 类绑定:A.x
      • 转换为:A.__dict__['x'].__get__(None,A)
    • 超绑定:super(a, A).x

实例绑定—资料描述器

  • 资料描述器:定义了__set____delete__方法
  • 非资料描述器:只定义了__get__方法
  • 访问对象属性时,描述器调用的优先级取决于描述器定义的方法

    • 优先级:资料描述器 > 实例字典属性 > 非资料描述器
    • 实例属性会重载非资料描述器
    • 实例属性和资料描述器同名时,优先访问描述器,否则优先 访问属性
  • 只读资料描述器:__set__raise AttributeError得到

描述器调用

todo

Python设计

  • function类中定义有__get__方法,则其实例(即函数) 都为非资料描述器

    • 所以实例可以覆盖、重载方法
    • __getattribute__会根据不同方法类型选择绑定对象
      • staticmethod:静态方法
      • classmethod:类方法
      • 实例方法
  • super类中定义有__get__方法,则其实例也为描述器

  • @property方法被实现为资料描述器

特殊描述器类

todo

  • wrapper_descripter<slot wrapper>,封装C实现的函数

    • 等价于CPython3中函数
    • 调用__get__绑定后得到<method-wrapper>
    • object的方法全是<slot wrapper>
  • method-wrapper<method-wrapper>,封装C实现的绑定方法

    • 等价于CPython3中绑定方法

function描述器类

function描述器类:实例化即得到函数

1
2
3
4
5
6
7
8
9
class function:
function(code, globals[, name[, argdefs[, closure]]])

def __call__(self, /, *args, **kwargs):
# 作为一个函数调用自身

def __get__(self, instance, owner, /):
# 返回`owner`类型实例`instance`的属性
# 即返回绑定方法

method描述器类

method描述器类:实例化即得到(bound )method,绑定方法

1
2
3
4
5
6
7
8
class method:
method(function, instance)

def __call__(self, /, *args, **kwargs):
# 作为函数调用自身

def __get__(self, instance, owner, /):
# 返回自身
  • (bound )method:绑定方法,(首个参数)绑定为具体实例 的函数,即实例属性

XXmethod描述类

  • 代码是C实现,这里是python模拟,和help结果不同
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class classmethod:
def __init__(self, method):
self.method = method
def __get__(self, obj, cls):
return lambda *args, **kw: self.method(cls,*args,**kw)

class staticmethod:
def __init__(self, callable):
self.f = callable
def __get__(self, obj, cls=None):
return self.f
@property
def __func__(self):
return self.f
  • 类中静态方法、类方法就是以上类型的描述器

    • 静态方法:不自动传入第一个参数
    • 类方法:默认传递类作为第一个参数
    • 描述器用途就是避免默认传入实例为第一个参数的行为
  • 静态方法、类方法均是非资料描述器,所以和实例属性重名时 会被覆盖

  • 所以类静态方法、类方法不能直接通过__dict__获取、调用, 需要调用__get__方法返回绑定方法才能调用

    • 直接访问属性则由__getattribute__方法代劳

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class Integer:
# 描述器类
def __init__(self, name):
self.name = name

def __get__(self, instance, cls):
# 描述器的每个方法会接受一个操作实例`instance`
if instance is None:
# 描述器只能定义为类属性,在这里处理直接使用类
# 访问描述器类的逻辑
return self
else:
return instance.__dict__(self.name)

def __set__(self, instance, value):
if not instance(value, int):
rasie TypeError("expect an int")
instance.__dict__[self.name] = value
# 描述器方法会操作实例底层`__dict__`属性

def __delete__(self, instance):
del instance.__dict__[self.name]

class Point:
x = Integer("x")
y = Integer("y")
# 需要将描述器的实例作为类属性放在类的定义中使用

def __init__(self, x, y):
self.x = x
self.y = y

def test():
p = Point(2, 3)
print(p.x)
# 调用`Point.x.__get__(p, Point)`
print(Point.x)
# 调用`Point.x.__get__(None, Point)`
p.y = 5
# 调用`Point.y.__set__(p, 5)`

自定义类创建

__init_subclass__

1
2
classmethod object.__init_subclass__(cls):
pass
  • 用途:派生类继承父类时,基类的__init_subclas__被调用
    • 可以用于编写能够改变子类行为的类
    • 类似类装饰器,但是类装饰其影响其应用的类,而 __init_subclass__影响基类所有派生子类
    • 默认实现:无行为、只有一个参数cls
    • 方法默认、隐式为类方法,不需要classmethod封装
  • 参数

    • cls:指向新的子类
    • 默认实现无参数,可以覆盖为自定义参数
    1
    2
    3
    4
    5
    6
    7
    class Philosopher:
    def __init_subclass__(self, default_name, **kwargs):
    super().__init_subclass__(**kwrags)
    cls.default_name = default_name

    class AstraliaPhilosopher(Philosopher, default_name="Bruce"):
    pass
    • 定义派生类时需要注意传递参数
    • 元类参数metaclass会被其他类型机制消耗,不会被传递 给__init_subclass__

元类

  • 默认情况下,类使用type构建

    • 类体在新的命名空间中执行,类名被局部绑定到 元类创建结果type(name, bases, namespace)
  • 可在类定义部分传递metaclass关键字参数,自定义类创建 过程

    • 类继承同样继承父类元类参数
    • 其他类定义过程中的其他关键字参数会在以下元类操作中 进行传递
      • 解析MRO条目
      • 确定适当元类
      • 准备类命名空间__prepare__
      • 执行类主体
      • 创建类对象

解释MRO条目

1
2
def type.__mro_entries__():
pass
  • 用途:若类定义中基类不是type的实例,则使用此方法对 基类进行搜索

    • 找到结果时,以原始基类元组作为参数进行调用
  • 返回值:类的元组替代基类被使用

    • 元组可以为空,此时原始基类将被忽略

元类确定

  • 若没有显式给出基类、或元类,使用type()
  • 若显式给出的元类不是type()的实例,直接用其作为元类
  • 若显式给出type()实例作为元类、或定义有基类,则选取 “最派生”元类
    • 最派生元类从显式指定的元类、基类中元类中选取
    • 最派生元类应为所有候选元类的子类型
    • 若没有满足条件的候选元类则raise TypeError

准备类命名空间

1
2
def type.__prepare__(name, bases, **kwds):
pass
  • 用途:确定合适的元类之后,准备类命名空间

    • 若元类没有__prepare__属性,类命名空间将被初始化为 空ordered mapping
  • 参数:来自于类定义中的关键字参数

执行类定义主体

1
2
exec(body, globals(), namespace)
# 执行类主体类似于
  • 普通调用和exec()区别
    • 类定义在函数内部时
      • 词法作用域允许类主体、方法引用来自当前、外部 作用域名称
      • 但内部方法仍然无法看到在类作用域层次上名称
      • 类变量必须通过实例的第一个形参、类方法方法

创建类对象

1
2
metaclass(name, base, namespace, **kwds):
pass
  • 用途:执行类主体填充类命名空间后,将通过调用 metaclass(name, base, namespace, **kwds)创建类对象

  • 参数:来自类定义中的关键字参数

说明
  • 若类主体中有方法中引用__class__super,则__class__ 将被编译器创建为隐式闭包引用

    • 这使得无参数调用super可以能基于词法作用域正确 定位类
    • 而被用于进行当前调用的类、实例则是基于传递给方法 的第一个参数来标识

自定义实例、子类检查

  • 以下方法应该的定义在元类中,不能在类中定义为类方法

    • 类似于实例从类中查找方法
  • 元类abc.ABCMeta实现了以下方法以便允许将抽象基类ABC 作为“虚拟基类”添加到任何类、类型(包括内置类型)中

__instancecheck__

1
2
def class.__instancecheck__(self, instance):
pass
  • 用途:若instance被视为class直接、间接实例则返回真值

    • 重载instance内置函数行为
  • 返回:布尔值

    • 内置钩子函数:isintance(instance, class)

__subclasscheck__

1
2
class.__subclasscheck__(self, subclass):
pass
  • 用途:若subclass被视为class的直接、间解子类则返回 真值

    • 重载issubclass内置函数行为
  • 返回:布尔值

    • 内置钩子函数:issubclass(subclass, class)

模拟范型类型

__class_getitem__

1
2
classmethod object.__class_getitem__(cls, key):
pass
  • 用途:按照key指定类型返回表示泛型类的专门化对象

    • 实现PEP 484规定的泛型类语法
    • 查找基于对象自身
    • 主要被保留用于静态类型提示,不鼓励其他尝试使用
    • 方法默认、隐式为类方法,不需要classmethod封装
  • 参数

    • cls:当前类
    • key:类型

模拟可调用对象

__call__

1
2
def object.__call__(self[,args...]):
pass
  • 用途:实例作为函数被调用时被调用
    • 若定义此方法x(arg1, arg2, ...)等价于 x.__call__(arg1, args2, ...)

模拟容器类型

  • collections.abc.MutableMapping为抽象基类
    • 其实现基本方法集__getitem____setitem____delitem__keys()
    • 可以方法继承、扩展、实现自定义映射类

__len__

1
2
def object.__len__(self):
pass
  • 用途:计算、返回实例长度

    • 若对象未定义__bool__,以__len__是否返回非0作为 布尔运算结果
  • 返回值:非负整形

    • 钩子函数:len()
  • CPython:要求长度最大为sys.maxsize,否则某些特征可能 会raise OverflowError

__length_hint__

1
2
def object.__length_hist__(self):
pass
  • 用途:返回对象长度估计值

    • 存粹为优化性能,不要求正确无误
  • 返回值:非负整形

    • 钩子函数:operator.length_hint()

__getitem__

1
2
def object.__getitem__(self, key):
pass
  • 用途:实现根据索引取值

  • 参数

    • 序列key:整数、切片对象

      • key类型不正确将raise TypeError
      • key在实例有效范围外将raise IndexError
    • 映射key:可hash对象

      • key不存在将raise KeyError
  • 返回值:self[key]

__setitem__

1
2
def object.__setitem__(self, key, value):
pass
  • 用途:实现根据索引赋值

  • 参数:同__geitem__

__delitem__

1
2
def object.__delitem(self, key):
pass
  • 用途:实现删除索引对应项

  • 参数:同__getitem__

__missing__

1
2
def object.__missing__(self, key):
pass
  • 用途:__getitem__无法找到映射中键时调用

__reversed__

1
2
def object.__iter__(self):
pass
  • 用途:为容器类创建逆向迭代器

  • 返回值:逆向迭代对象

    • 内置钩子函数:reversed()

说明

  • 若未提供__reversed__方法,reversed函数将回退到使用 序列协议__len____getitem__

  • 支持序列协议的对象应该仅在能够提供比reversed更高效实现 时才提供__reversed__方法

__contains__

1
2
def object.__contains__(self, item):
pass
  • 用途:实现成员检测

    • itemself成员则返回True、否则返回False
    • 对映射应检查键
  • 返回值:布尔值

    • 钩子运算:in

说明

  • 若未提供__contains__方法,成员检测将依次尝试

    • 通过__iter__进行迭代
    • 使用__getitem__旧式序列迭代协议
  • 容器对象可以提供更有效率的实现

模拟数字

数字运算

定义以下方法即可模拟数字类型

  • 特定类型数值类型不支持的运算应保持未定义状态
  • 若不支持与提供的参数进行运算,应返回NotImplemented
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
def object.__add__(self, other):
# `+`
def object.__sub__(self, other):
# `-`
def object.__mul__(self, other):
# `*`
def object.__matmul__(self, other):
# `@`
def object.__truediv__(self, other):
# `/`
def object.__floordiv__(self, other):
# `//`
def object.__mod__(self, other):
# `%`
def object.__divmod__(self, other):
# `divmod()`
def object.__pow__(self, other[, modulo=1]):
# `pow()`/`**`
# 若要支持三元版本内置`pow()`函数,应该接受可选的第三个
# 参数
def object.__lshift__(self, other):
# `<<`
def object.__rshift__(self, other):
# `>>`
def object.__and__(self, other):
# `&`
def object.__or__(self, other):
# `|`
def object.__xor__(self, other):
# `~`

反射二进制算术运算

以下成员函数仅在左操作数不支持相应运算且两操作数类型不同时被调用

  • 实例作为作为相应运算的右操作数

  • 若右操作数类型为左操作数类型子类,且字类提供如下反射方法

    • 右操作数反射方法优先于左操作数非反射方法被调用
    • 允许子类覆盖祖先类运算符
  • 三元版pow()不会尝试调用__rpow__(转换规则太复杂)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
def object.__radd__(self, other):
# `+`
def object.__rsub__(self, other):
# `-`
def object.__rmul__(self, other):
# `*`
def object.__rmatmul__(self, other):
# `@`
def object.__rtruediv__(self, other):
# `/`
def object.__rfloordiv__(self, other):
# `//`
def object.__rmod__(self, other):
# `%`
def object.__rdivmod__(self, other):
# `divmod()`
def object.__rpow__(self, other[, modulo=1]):
# `pow()`/`**`
# 若要支持三元版本内置`pow()`函数,应该接受可选的第三个
# 参数
def object.__rlshift__(self, other):
# `<<`
def object.__rrshift__(self, other):
# `>>`
def object.__rand__(self, other):
# `&`
def object.__ror__(self, other):
# `|`
def object.__rxor__(self, other):
# `~`

扩展算术赋值

实现以下方法实现扩展算数赋值

  • 以下方法应该尝试对自身进行操作

    • 修改self、返回结果(不一定为self
  • 若方法未定义,相应扩展算数赋值将回退到普通方法中

  • 某些情况下,扩展赋值可导致未预期错误

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
def object.__iadd__(self, other):
# `+=`
def object.__isub__(self, other):
# `-=`
def object.__imul__(self, other):
# `*=`
def object.__imatmul__(self, other):
# `@=`
def object.__itruediv__(self, other):
# `/=`
def object.__ifloordiv__(self, other):
# `//=`
def object.__imod__(self, other):
# `%=`
def object.__ipow__(self, other[, modulo=1]):
# `**=`
def object.__ilshift__(self, other):
# `<<=`
def object.__irshift__(self, other):
# `>>=`
def object.__iand__(self, other):
# `&=`
def object.__ior__(self, other):
# `|=`
def object.__ixor__(self, other):
# `~=`

一元算术运算

1
2
3
4
5
6
7
8
def object.__neg__(self):
# `-`
def object.__pos__(self):
# `+`
def object.__abs__(self):
# `abs()`
def object.__invert__(self):
# `~`

类型转换运算

1
2
3
4
5
6
def object.__complex__(self):
# `complex()`
def object.__int__(self):
# `int()`
def object.__float__(self):
# `float()`

整数

1
2
def object.__index__(self):
pass
  • 存在此方法表明对象属于整数类型

    • 必须返回整数
    • 为保证以一致性,同时也应该定义__int__(),两者返回 相同值
  • 调用此方法以实现operator.index()、或需要无损的转换为 整数对象

    • 作为索引、切片参数
    • 作为bin()hex()oct()函数参数

精度运算

1
2
3
4
5
6
7
8
def object.__round__(self[, ndigits]):
# `round()`
def object.__trunc__(self):
# `math.trunc()`
def object.__floor__(self):
# `math.floor()`
def object.__ceil__(self):
# `math.ceil()`
  • 返回值:除__round__中给出ndigits参数外,都应该为 原对象截断为Integral(通常为int

  • 若未定义__int__,则int回退到__trunc__

元属性查找

  • 元属性查找通常会绕过__getattribute__方法,甚至包括元类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    class Meta(type):
    def __getattribute__(*args):
    print("Metaclass getattribute invoked")
    return type.__getattribute__(*args)

    class C(object, metaclass=Meta):
    def __len__(self):
    return 10
    def __getattribute__(*args):
    print("Class getattribute invoked")
    return object.__geattribute__(*args)

    if __name__ == "__main__":
    c = C()
    c.__len__()
    # 通过实例显式调用
    # 输出`Class getattribute invoked\n10"
    type(c).__len__(c)
    # 通过类型显式调用
    # 输出`Metaclass getattribute invoked\n10"
    len(c)
    # 隐式查找
    # 输出`10`
    • 为解释器内部速度优化提供了显著空间
    • 但是牺牲了处理特殊元属性时的灵活性
      • 特殊元属性必须设置在类对象本身上以便始终一致地 由解释器发起调用
  • 隐式调用元属性仅保证元属性定义在对象类型中能正确发挥 作用

    1
    2
    3
    4
    5
    6
    7
    8
    class C:
    pass

    if __name__ == "__main__":
    c = C()
    c.__len__() = lambda: 5
    len(c)
    # `rasie TypeError`
    • 元属性定义在实例字典中会引发异常
    • 若元属性的隐式查找过程使用了传统查找过程,会在对类型 对象本身发起调用时失败
    • 可以通过在查找元属性时绕过实例避免

      1
      2
      >>> type(1).__hash__(1) == hash(1)
      >>> type(int).__hash__(int) == hash(int)

上下文管理器协议

上下文管理器:定义了在执行with语句时要建立的运行时上下文 的对象

  • 上下文管理器为执行代码块,处理进入、退出运行时所需上下文

    • 通常使用with语句调用
    • 也可以直接调用协议中方法方法
  • 典型用法

    • 保存、恢复各种全局状态
    • 锁、解锁资源:避免死锁
    • 关闭打开的文件:自动控制资源释放
  • 可利用contextlib模块方便实现上下文管理器协议

__enter__

1
2
def contextmanager.__enter__(self):
pass
  • 用途:创建、进入与当前对象相关的运行时上下文

    • 在执行with语句块前设置运行时上下文
  • 返回值

    • with子句绑定方法返回值到as子句中指定的目标,如果 方法返回值

__exit__

1
2
def contextmanger.__exit__(self, exc_type, exc_value, traceback):
pass
  • 用途:销毁、退出关联到此对象的运行时上下文

    • with语句块结束后,__exit__方法触发进行清理工作
    • 不论with代码块中发生什么,即使是出现异常, __exit__控制流也会执行完
  • 参数:描述了导致上下文退出的异常,正常退出则各参数为 None

    • exc_type
    • exc_value
    • traceback
  • 返回值:布尔值

    • 若上下文因异常退出
      • 希望方法屏蔽此异常(避免传播),应该返回真值, 异常被清空
      • 否则异常在退出此方法时将按照正常流程处理
    • 方法中不应该重新引发被传入的异常,这是调用者的责任

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
from socket import socket, AF_INET, SOCK_STREAM

class LazyConnection:
def __init__(self, address, family=AF_INET, type=SOCK_STREAM):
self.address = address
self.family = family
self.type = type
self.connections = []

def __enter__(self):
sock = socket(self.family, self.type)
sock.connect(self.address)
self.connections.append(sock)
return self.sock

def __exit__(self, exc_ty, exc_val, tb):
self.connections.pop().close()

from functools import partial

def test():
conn = LazyConnection("www.python.org", 80))
with conn as s1:
# `conn.__enter___()` executes: connection opened
s.send(b"GET /index.html HTTP/1.0\r\n")
s.send(b"Host: www.python.org\r\n")
s.send(b"\r\n")
resp = b"".join(iter(partial(s.recv, 8192), b""))
# `conn.__exit__()` executes: connection closed

with conn as s2:
# 此版本`LasyConnection`可以看作是连接工厂
# 使用列表构造栈管理连接,允许嵌套使用
pass

迭代器协议

  • 可迭代对象:实现__iter__方法的对象
  • 迭代器对象:同时实现__next__方法的可迭代对象
  • 使用collections.abc模块判断对象类型

__iter__

1
2
def object.__iter__(self):
pass
  • 用途:创建迭代器对象,不负责产生、返回迭代器元素

    • 容器对象要提供迭代须实现此方法
      • 容器支持不同迭代类型,可以提供额外方法专门请求 不同迭代类型迭代器
    • 迭代对象本身需要实现此方法,返回对象自身
      • 允许容器、迭代器均可配合for...in...语句使用
  • 返回值:迭代器对象

    • 映射类型应该逐个迭代容器中键
    • 内置钩子函数:iter()
  • 此方法对应Python/C API中python对象类型结构体中 tp_iter槽位

__next__

1
2
def object.__next__():
pass
  • 用途:从迭代器中返回下一项

    • 若没有项可以返回,则raise StopIteration
    • 一旦引发raise StopIteration,对后续调用必须一直 引发同样的异常,否则此行为特性无法正常使用
  • 返回值:迭代器对象中下个元素

    • 映射类型返回容器中键
    • 内置钩子函数:next()
  • 此方法对应Python/C API中python对象类型结构体中 tp_iternext槽位

协程/异步

__await__

1
2
def object.__await__(self):
pass
  • 用途:用于实现可等待对象

  • 返回值:迭代器

    • 钩子运算:await
  • asyncio.Future实现此方法以与await表达式兼容

Awaitable Objects

可等待对象:异步调用句柄,等待结果应为迭代器

  • 主要是实现__await__方法对象

    • async def函数返回的协程对象
  • type.coroutine()asyncio.coroutine()装饰的生成器 返回的生成器迭代器对象也属于可等待对象,但其未实现 __await__

  • 协程对象参见cs_python/py3ref/dm_gfuncs
  • py3.7前多次await可等待对象返回None,之后报错

异步迭代器协议

  • 异步迭代器常用于async for语句中
  • 其他参见迭代器协议

__aiter__

1
2
def object.__aiter__(self):
pass
  • 用途:返回异步迭代器对象,不负责产生、返回迭代器元素
    • 返回其他任何对象都将raise TypeError
  • 其他参见__iter__方法

__anext__

1
2
async def object.__anext__(self):
pass
  • 返回:从异步迭代器返回下个结果值

    • 迭代结束时应该raise StopAsyncIteration
  • 用途

    • 在其中调用异步代码
  • 其他参见__next__方法

1
2
3
4
5
6
7
8
9
10
class Reader:
async def readline(self):
pass
def __aiter__(self):
return self
async def __anext__(self):
val = await self.readline()
if val == "b":
raise StopAsyncIteration
return val

异步上下文管理器协议

  • 异步上下文管理器常用于async with异步语句中
  • 其他参见上下文管理器协议

__aenter__

1
2
async def object.__aenter__(self):
pass
  • 用途:异步创建、进入关联当前对象的上下文执行环境

    • async def定义为协程函数,即在创建上下文执行环境 时可以被挂起
  • 返回:可等待对象

  • 其他参见__enter__

__aexit__

1
2
async def object.__aexit__(self):
pass
  • 用途:异步销毁、退出关联当前对象的上下文执行环境

    • async def定义为协程函数,即在销毁上下文执行环境 时可以被挂起
  • 返回:可等待对象

  • 其他参见__exit__函数

综述

Custom Classes

用户定义类:通过类定义创建

  • 每个类通过字典对象__dict__实现独立的命名空间

    • 类属性引用被转化为在此字典中查找
    • 其中未发现属性名时,继续在基类中查找
      • 基类查找使用C3方法解析顺序,即MRO列表
    • 也存在一些钩子对象允许其他定位属性的方式
  • 当类属性引用yield类方法对象时,其将转化为__self__ 属性为当前类对象的实例方法对象

  • 当类属性引用yield静态方法对象时,其将转换为静态方法 对象所封装的对象

  • 类属性复制会更新类字典,不会更新基类字典

  • 类对象可被调用产生类实例

特殊属性

  • __bases__:包含基类的元组,依在基类列表中出现的顺序

Class Instances

类实例:通过调用类对象创建

  • 每个类实例都有一个通过字典对象__dict__实现的独立命名 空间

    • 属性引用首先在此字典中查找
    • 其中未发现属性名时,继续在对应类属性中查找

      • 用户定义函数对象:其会被转化为实例方法对象
        • __self__属性即为该实例
      • 静态方法、类方法对象:同样会被转化
      • 描述器属性有特殊处理,实际存放在类__dict__中 对象不同
    • 若未找到类属性,对象对应类具有__getattr__()方法, 将调用该方法
  • 属性赋值、删除会更新实例字典,不会更新对应类字典

    • 若类具有__setattr____delattr__方法,将调用方法 而不是直接更更新对应实例字典

特殊属性

  • __class__:实例对应类

Classes

类:类对象通常作为“工厂”创建自身实例

  • __doc__:类的文档字符串
    • 类定义第一条语句、且须为字符串字面值
    • 没有则为None
    • 不会被子类继承

Class Instances

类实例:在所属类中定义__call__()方法即成为可调用对象

属性

属性访问.

  • A.attr被解释为type(A)中__getattribute__(A, attr)

    • .的行为由python解释器定义
    • type(A)中__getattribute__用于强调不会再从 type(type(A))继续获取调用__getattibute__
  • 定义在类命名空间中函数是为实例定义

    • 要为类定义方法应该自定义元类
  • 测试代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    class Meta(type):
    def __getattribute__(self, attr):
    print("--Meta--", attr)
    return super().attr

    class D(metaclass=Meta):
    def __getattribute__(self, attr):
    print("--Class--", attr)
    return super().attr
  • __getattribute__函数说明参见 cs_python/py3ref/special_methods

属性访问控制

  • python没有没有属性访问控制,不依赖语言特性封装数据,而是 遵循一定属性、方法命名规约达到效果
  • __开头属性:属性名称会被修改

    • 防止被派生类继承,此类属性无法通过继承覆盖
    • 即若清楚代码会涉及子类,且应该在子类中隐藏起来,考虑 使用双下划线开头
    • 通常是在属性名称前添加类名标记_cls
    • 但同时以__结尾属性名称不会被修改
  • _开头属性:应被视为私有属性,不应被外部访问

    • python无法真正防止访问内部名称,但是这样会导致脆弱的 代码
    • 此约定同样适用于模块名、模块级别函数

      • 默认情况下,通配符*不会导入模块私有属性,除非 在配置有__all__属性
      • 导入参见cs_python/py3ref/simple_stmt#todo
  • _结尾:避免定义的变量和保留关键字冲突

特殊属性

  • __dict__:命名空间包含的属性
  • __doc__:文档字符串
    • 第一条语句、且须为字符串字面值
    • 没有则为None
    • 不会被子类继承
  • __name__:名称
  • __qualname__qualified name,完整限定名称
    • 以点号分隔的名称
    • 显示模块全局作用域到模块中某个定义类、函数、方法的 路径
  • __module__:所属模块名称
    • 没有则为None

描述器属性

  • 描述器协议参见cs_python/py3ref/special_methods
  • 实例/类/静态方法:参见cs_python/py3ref/dm_gfuncs

@property

@property装饰器:为类的属性增加处理逻辑,如:类型检查、 合法性验证

  • property属性和普通属性实现迥异,但使用类似

    • property属性就是绑定有这些处理逻辑函数的类实例
    • 访问、赋值、解除绑定时会自动触发gettersetterdeleter处理逻辑
  • property属性(或者说有效描述器)为类属性

    • 一般需要通过在实例、或描述器命名空间 instance.__dict__中存储数据,以实现对实例操作逻辑 独立
    • 也可以实时计算属性值,此时无需为实例分别存储数据
    • 初始化时,不应该直接设置底层数据属性,会绕过setter 的参数检查
  • 过度使用@property时会降低代码可读性、效率,使用 get/set方法可能有更好的兼容性

代码实现

  • 代码是C实现,这里是python模拟,和help结果不同
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class Property(object):
"Emulate PyProperty_Type() in Objects/descrobject.c"

def __init__(self, fget=None, fset=None, fdel=None, doc=None):
self.fget = fget
self.fset = fset
self.fdel = fdel
if doc is None and fget is not None:
doc = fget.__doc__
self.__doc__ = doc

def __get__(self, obj, objtype=None):
if obj is None:
return self
if self.fget is None:
raise AttributeError("unreadable attribute")
return self.fget(obj)

def __set__(self, obj, value):
if self.fset is None:
raise AttributeError("can't set attribute")
self.fset(obj, value)

def __delete__(self, obj):
if self.fdel is None:
raise AttributeError("can't delete attribute")
self.fdel(obj)

def getter(self, fget):
return type(self)(fget, self.fset, self.fdel, self.__doc__)
# 返回描述器,可省略

def setter(self, fset):
return type(self)(self.fget, fset, self.fdel, self.__doc__)
# 返回更新`fset`的描述器,同名所以覆盖前者

def deleter(self, fdel):
return type(self)(self.fget, self.fset, fdel, self.__doc__)
  • @property是描述器类,接受方法返回同名资料描述器

创建property属性

  • @property[.getter]装饰getter-like方法得到同名资料 描述器

  • 返回描述器包含.setter().deleter()方法/装饰器进一步 完善描述器

    • @method.setter:为描述器完善赋值处理逻辑
    • @method.deleter:为描述器完善del处理逻辑
  • 可以直接使用已有类中函数创建property类实例,得到 property属性(描述器)

  • 派生类中property属性覆盖

    • 派生类中直接使用@property创建同名属性会覆盖基类 中property属性

      • 只有显式声明的处理逻辑被设置
      • 基类中逻辑位于基类相应同名property属性,不会 被“隐式继承”
    • @<basecls>.<method>.getter/setter/deleter单独覆盖 property属性方法

      • 但是basecls硬编码方式,必须知道定义 property属性的具体类(或其子类)
  • 描述器协议、实现参见cs_python/py3ref/special_methods

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
class Student(object):

def __init__(self, value):
self.birth = value
# 使用`self.birth`而不是`self._birth`,保证即使
# 实在初始化时仍然进行参数检查

@property
# 将一个getter-like方法变为属性
# `@property`同时会创建装饰器`@method.setter`
def birth(self):
return self._birth

@birth.setter
# `@property`对应,将setter-like方法变为属性
def birth(self, value):
if not instance(value, int):
raise ValueError("birth must be an integer")
if value < 1900 or value > 2020:
raise ValueError("birth must between 1900 ~ 2020")
self._birth = value

@birth.deleter
# 同`@property`对应,在`del`时调用
def birth(self):
del(self._age)
del(self._birth)

@property
# 只设置`@property`而没有设置对应`@birth.setter`
# 这样`birth`就成了只读属性
def age(self):
return 2018 - self._birth

def get_first_name(self):
return self._first_name

def set_first_name(self):
if not instance(value, str):
raise TypeError("expected a string")
self._first_name = value

def del_first_name(self):
raise AttributeError("can't delete attribute")

name = property(get_first_name,
set_first_name,
del_first_name)
# 在已有getter-like、setter-like方法上创建property
# 注意:这里就是应该定义类属性,本身使用`@property`
# 装饰器也是相当于创建类属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
class Person:
def __init__(self, name):
self.name = name

@property
def name(self):
return self._name

@name.setter
def name(self, value):
if not instance(value, str):
raise TypeError("expected a string")
self._name = value

@name.deleter
def name(self):
raise AttributeError("can't delete attribute")

class SubPersonAll(Person):
# 这个类继承、扩展了`name`属性的所有功能
@property
def name(self):
print("getting name")
return super().name

@name.setter
def name(self, value):
print("Setting name to", value)
super(SubPerson, SubPerson).name.__set__(self, value)
# 使用`super(SubPerson, SubPerson)`调用父类实现
# 将控制权传递给`.name.__set__`方法,委托给父类
# 中定义的setter方法

@name.deleter
def name(self):
print("deleting name")
super(SubPerson, SubPerson).name.__delete__(self)

class SubPersonPart(Person):
# 仅修改`name`属性的某个方法
# 需要知道定义`name`属性的基类,否则重新定义property属性
# 的所有方法,并使用`super`将控制权转移给父类
@Person.name.getter
# 使用硬编码的`Person`类名,这样会把之前已经定义的
# property属性方法复制过来,而对应的`getter`、
# `setter`、`deleter`方法被替换
# 这里如果直接使用`@property`装饰,那么`setter`、
# `deleter`方法将会消失
def name(self):
print("getting name")
return super().name

类继承

  • 类继承会获得基类的所有方法
    • 类里面的方法其实真的不是给类使用的,而是给实例使用
    • 类自身使用的方法是元类中的方法

Method Resolution Order

MRO/方法解析顺序列表:包含当前类所有超类的线性顺序表

  • MRO列表顺序通过C3线性化算法实现,对每个类按以下规则合并 所有父类的MRO列表

    • 子类先于父类检查
    • 多个父类根据其在列表中的顺序被检查
    • 若对下一个类存在多个合法的选择,选择第一个父类
  • 为了实现继承,python会在MRO列表上从左到右开始查找超类, 直到第一个匹配这个属性的类为止

  • 可以通过__mro__mro()访问

super

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class super:
super()
# 等同于:`super(__class__, <first_argument>)`
# `<first_argument>`常常就是`self`
super(type)
# 返回:未绑定super对象,需要`__get__`绑定
super(type, obj)
# 返回:已绑定super对象,要求`isinstance(obj,type)`
super(type, type2)
# 返回:已绑定super对象,要求`issubclass(type2, type)`
# 此时调用方法返回是函数,不是绑定方法,不会默认传入
# `type2`作为首个参数

def __get__(self, obj, type=None):


def super(cls, inst/subcls):
mro = inst.__class__.mro()
mro = subcls.mro()
return mro[mro.index(cls) + 1]
  • 参数

    • 第一个参数:在MRO列表中定位类搜索起点(不包括)
    • 第二个参数:提供MRO列表
      • 类:直接传递MRO列表
      • 实例:传递所属类的MRO列表
  • 返回:封装有两个参数的super实例

    • 类似于返回MRO列表中某个类的实例,取决于访问的属性
  • 用途:依次遍历MRO列表(指定位置开始)中类,查找指定属性

    • 可以使用指定超类创建super实例,跳过对部分类搜索
    • 只有MRO列表中每个类中的方法都super()调用,才能保证 列表中所有类的该方法都被链式调用

说明

  • 需要注意super(cls, inst).__getattribute__("meth")中 共有两段属性访问,两次访问调用不同__getattribute__

    • super(cls, inst).__getattribute__首先调用 super.__getattribute__type(inst).mro()中寻找 some_cls.__getattribute__

    • 然后调用some_cls.__getattrbibute__("meth")访问 meth属性

  • 应使用super访问基类属性,而不是直接使用基类名称,避免 多继承中出现问题

    • 继承链super保证方法只按找MRO列表顺序调用一次
    • 多继承中硬编码基类名称调用方法可能导致方法被调用多次
  • super访问的属性路线不够明确,所以需要遵循以下原则

    • 继承体系中,所有相同名字的方法拥有可兼容的参数名, 比如:相同参数个数、名称
    • 最好确保最顶层类提供这个方法的实现,这样保证MRO上的 查找链肯定可以找到该方法

抽象类

接口、抽象类

  • 抽象类无法直接实例化

    • 目的就是让别的类继承它并实现特定的抽象方法
    • 也可以通过注册方式让某类实现抽象基类
  • 用途

    • 通过执行类型检查,确保实现为特定类型、实现特定接口
  • 类型检查很方便,但是不应该过度使用,因为动态语言目的就是 灵活性,强制类型检查让代码更复杂
  • 使用abc模块方便定义抽象类

Mixins

Mixins:把一些有用的方法包装成Mixin类,用于扩展其他类的 功能,而这些类往往又没有继承关系

  • Mixin不能直接实例化使用
  • Mixin没有自己的状态信息,即没有定义__init__方法, 没有实例属性,因此Mixin中往往会定义__slots__ = ()
  • Mixins讨论参见cs_program/program_design/inheritation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
class LoggedMappingMixin:
__slots__ = ()
def __getitem__(self, key):
print("getting:", str(key))
return super9).__getimte__(key)

def __setitem__(self, key, value):
print("setting {} = {!r}".format(key, value))
return super().__setitem__(key, value)

def __delitem__(self, key):
print("deleting", str(key))
return super().__delitem__(key)

class SetOnceMappingMixin:
__slots__ = ()
def __setitem__(self, key, value):
if key in self:
raise KeyError(str(key), "alreay set")
return super().__setitem__(key, value)

class StringKeysMappingMixin:
__slots__ = ()
def __setitem__(self, key, value):
if not isinstance(key, str):
raise TypeError("keys must be strings")
return super().__setitem__(key, value)

# 单独的Mixin类使用没有意义,也无法实例化

class LoggedDict(LoggedMappingMixin, dict):
# 把混入类和其他已存在的类结合起来使用
pass

from collections import defaultdict

class SetOnceDefaultDict(SetOnceMappingMixin, defaultdict):
pass

def test():
d = LoggedDict()
d["x"] = 23
print(d["x"])
del d["x"]

d = setOnceDefaultDict(list)
d["x"].append(2)

Python类用法实例

延迟计算

使用描述器类构造延迟计算属性

  • 主要目的是为了提升性能
  • 避免立即计算
  • 缓存计算结果供下次使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
class lazyproperty:
def __init__(self, func):
self.func = func

def __get__(self, instance, cls):
if instance is None:
return self
else:
value = self.func(instance)
setattr(instance, self.func.__name__, value)
# 计算完成之后,缓存计算结果于类实例中
return value
# 描述器仅仅定义一个`__get__`方法,比通常具有更弱的绑定
# 这里,只有被访问属性不在**实例**底层字典`__dict__`中时
# `__get__`方法才会被触发
# 描述器属性是类属性,但优先级好像是高于实例属性???

def lazyproperty_unchangable(func):
# 这个版本的延迟计算,使用property属性限制对结果的修改
name = "_lazy_" + func.__name __

@property
# 这里`@property`是在类外定义的
# 并且,这里实际上返回的是`property`类实例,也不需要
# `wraps(func)`保持原函数元信息
# 但此时所有的操作都定向到`getter`函数上,效率较低
def lazy(self):
if hasattr(self, name):
return getattr(self, name)
else:
value = func(self)
setattr(self, name, value)
return value
return lazy

import math

class Circle:
def __init__(self, radiu):
self.radius = radius

@lazyproperty
def area(self):
print("computing area")
return math.pi * self.radius ** 2
# 等价于`area = lazyproperty(area)`
# 所以是真的把描述器类实例作为类属性

@lazyproperty
def perimeter(self):
print("computing perimeter")
return 2 * math.pi * self.radius

数据模型类型约束

使用描述器在对实例某些属性赋值时进行检查

类继承方案

基础构建模块

创建数据模型、类型系统的基础构建模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Descriptor:
def __init__(self, name=None, **opts):
self.name = name
for key, value in opts.items()
setattr(self, key, value)

def __set__(self, instance, value):
if instance is None:
return self
instance.__dict__[self.name] = value

class Typed(Descriptor):
def __set__(self, instance, value):
if value < 0:
raise ValueError("expected >= 0")
super().__set__(instance, value)

class Unsigned(Descriptor):
def __set__(self, instance, value):
if value < 0:
raise ValueError("expect >= 0")
super().__set__(instance, value)

class MaxSized(Descriptor):
def __init__(self, name=None, **opts):
if "size" not in opts:
raise TypeError("missing size options")
super.__init__(name, **opts)

def __set__(self, instance, value):
if len(value) >= self.size:
raise ValueError("size must be <" + str(self.size))
super().__set__(instance, value)

具体数据类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Integer(Typed):
expected_type = int

class UsignedInteger(Integer, Unsigned):
# 描述器类是基于混入实现的
pass

class Float(Typed):
expected_type = Float

class UnsignedFloat(Float, Unsigned):
pass

class String(Typed):
expected_type = str

class SizedString(String, MaxSized):
pass

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
class Stock:
name = SizedString("name", size=8)
shares = UnsignedInteger("shares")
price = UnsignedFloat("price")

def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price

def check_attributes(**kwargs):
def decrator(cls):
for key, value in kwargs.items():
if isinstance(value, Descriptor):
value.name = key
setattr(cls, key, value)
else:
setattr(cls, key, value(key))
return cls
return decrator

@check_attributes(name=SizedString(size=8),
shares=UnsignedInteger,
price=UnsignedFloat)
# 使用类装饰器简化版本
class Stock2:
def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price

class checkmeta(type):
def __new__(cls, clsname, bases, methods):
for key, value in method.items():
if isinstance(value, Descriptor):
value.name = key
return type.__new__(cls, clsname, bases, methods)

class Stock3(metaclass=checkdmeta):
name = SizedString(size=8)
shares = UnsignedInteger()
price = UnsignedFloat()

def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price

装饰器类替代mixin

使用类装饰器、元类都可以简化代码,但类装饰器更加灵活

  • 类装饰器不依赖其他任何新技术
  • 类装饰器可以容易的添加、删除
  • 类装饰器能做为mixin的替代技术实现同样的效果,而且速度 更快,设置一个简单的类型属性值,装饰器要快一倍

示例1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
def LoggedMapping(cls):
cls_getitem = cls.__getitem__
cls_setitem = cls.__setitem__
cls_delitem = cls.__setitem__
# 获取原`cls`的方法,避免死循环调用

def __getitem__(self, key):
print("getting", str(key))
return cls_getitem(self, key)
# 这里使用之前获取的方法指针调用,而不直接使用
# `cls.__getitem__`避免死循环

def __setitem__(self, key, value):
pritn("setting {} = {!r}", str(key))
return cls_set(self, key, value)

def __delitem__(self, key):
print("deleting", str(key))
return cls_delitem(self, key)

cls.__getitem__ = __getitem__
cls.__setitem__ = __setitem__
cls.__delitem__ = __delitem__

return cls

@LoggedMapping
class LoggedDict(dict):
pass

示例2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
def Type(expected_type, cls=None):
if cls is None:
return lambda cls: Typerd(expected_type, cls)
super_set = cls.__set__

def __set__(self, instance, value):
if no instance(value, expected_type):
raise TypeError("expect " + str(expected_type))
super_set(self, instance, value)

cls.__set__ = __set__
return cls

def Unsigned(cls):
super_set = cls.__set__

def __set__(self, instance, value):
if value < 0:
raise TypeError("missing size option")
super_set(self, name, **opts)

cls.__init__ = __set__
return cls

def MaxSized(cls):
super_init = cls.__init__

def __init__(self, name=None, **opts):
if "size" not in opts:
raise TypeError("missing size option")
super_init(self, name, **opts)

cls.__init__ = __init__

super_set = cls.__set__

def __set__(self, instance, value):
if len(value) >= self.size:
raise ValueError("size must be <" + str(self.size))
super_set(self, instance, value)

cls.__set__ = __set__
return cls

@Typed(int)
class Integer(Descriptor):
pass

@Unsigned
class UnsignedInteger(Integer):
pass

@Typed(float)
class Float(Descriptor):
pass

@Unsigned
class UnsignedFloat(Float):
pass

@Typed(str)
class String(Descriptor):
pass

@MaxSized
class SizedString(String):
pass

自定义容器

collections定义了很多抽象基类,可以用于定义自定义基类

collections.Sequence

Sequence需要实现的抽象方法有:

  • __getitem__
  • __len__
  • add

继承自其的类,支持的常用操作:索引、迭代、包含判断、切片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
from collections import Sequence
import collections

class SortedItems(Sequence):
# 必须要实现所有抽象方法,否则报错
def __init__(self, initial=None):
self._items = sorted(initial) if initial is not None else [ ]
def __getitem__(self, index):
return self._items[index]

def __len__(self):
return len(self._items)

def add(self, item):
bisect.insort(self._items, item)
# `bisect`模块是用于在排序列表中高效插入元素
# 保证在元素插入之后仍然保持顺序

# `SortedItems`继承了`colllections.Sequence`,现在和
# 普通序列无差,支持常用操作:索引、迭代、包含判断、切片

def test():
items = SortedItems([5, 1, 3])
print(list(items))
print(items[0], items[-1])
items.add(2)
print(list(items))

if instance(items, collections.Iterable):
pass
if instance(items, collections.Sequence):
pass
if instance(items, collections.Container):
pass
if instance(items, collections.Sized):
pass
if instance(items, collections.Mapping):
pass

collections.MutableSequence

MutableSequence基类包括需要实现的抽象方法

  • __getitem__
  • __setitem__
  • __delitem__
  • __len__
  • insert

提供的可用方法包括;

  • append
  • count:统计某值出现的次数
  • remove:移除某值的元素
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
from collections import MutableSequence

class Item(collections.MutableSequence):
def __init__(self, initial=None):
self._items = list(initial) if initial is not None else [ ]

def __getitem__(self, index):
print("getting:", index)
return self._items[index]

def __setitem__(self, index):
print("setting:", index)
self._items[index] = value

def __delitem__(self, index):
print("deleting:", index)
del self._items[index]

def insert(self, index, value):
print("inserting:", index, value)
self._items.insert(index, value)

def __len__(self):
print("len")
return len(self._items)

# 基本支持几乎所有的核心列表方法:`append`、`remove`、
# `count`

def count():
a = Items([1, 2, 3])
print(len(a))
a.append(4)
# 在末尾添加元素
# 调用了`__len__`、`insert`方法
a.count(2)
# 统计值为`2`出现的次数
# 调用`__getitem__`方法
a.remove(3)
# 删除值为`3`的元素
# 调用`__getitem__`、`__delitem__`方法

属性的代理访问

代理是一种编程模式,将某个操作转移给另一个对象来实现

  • 需要代理多个方法时,可以使用__getattr__

    • __getattr__方法只在属性、方法不存在时才被调用, 所以代理类实例本身有该属性不会触发该方法,也不会代理 至被代理类

    • 如果需要管理所有对方法、属性的访问,可以定义 __getattribute__,其在对类的所有属性、访问时均会 被触发,且优先级高于__getattr__

    • __setattr____delattr__需要约定是对代理类还是 被代理类操作,通常约定只代理_开头的属性,即代理类 只暴露被代理类公共属性

    • 注意:对象的元信息直接访问能通过__getattr__代理, 但是对应的hook可能无法正常工作,如果需要,要单独为 代理类实现元信息代理方法

  • 通过自定义属性访问方法,可以用不同方式自定义代理类行为, 如:日志功能、只读访问

  • 代理有时候可以作为继承的替代方案:代理类相当于继承了被 代理类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
class Proxy:
# 这个类用于包装、代理其他类,修改其行为
def __init__(self, obj):
self._obj = obj

def __getattr__(self, name):
print("getattr:", name)
return getattr(self._obj, name)

def __setattr__(self, name, value):
if name.startwith("_"):
# 约定只代理不以`_`开头的属性
# 代理类只暴露被代理类的公共属性
super().__setattr__(name, value)
else:
print("setattr:", name, value)
setattr(self._obj, name, value)

def __delattr__(self, name):
if name.startwith("_"):
super().__delattr__("name")
else:
print("delattr:", name)
delattr(self._obj, name)

class Spam:
def __init__(self, x):
self.x = x

def bar(self, x):
print("Spam.bar", self.x, y)

def test():
s = Spam(2)
p = Proxy(s)
p.bar(3)
p.x = 37
# 通过`p`代理`s`

p = Porxy([1, 3, 5])
# len(p)
# `len`函数直接使用会报错
p.__len__()
# `p.__len__`可以正常代理,返回代理的列表长度
# 这说明python中的钩子函数有特殊的结构?

状态机(状态模式)

为不同的状态定义对象,而不是使用过多的条件判断

  • 提高执行效率
  • 提高代码可维护性、可读性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
class Connection:
def __init__(self):
self.new_state(ClosedConnectionState)

def new_state(self, newstate):
self._state = newstate

def read(self):
return self._state.read(self)

def write(self, data):
return self._state.write(self, data)

def open(self):
return self._state.close(self)

class ConnectionState:
@staticmethod
def read(conn):
raise NotImplementedError()

@staticmethod
def write(conn, data):
raise NotImplementedError()

@staticmethod
def open(conn):
raise NotImplementedError()

@staticmethod
def close(conn):
raise NotImplementedError()

class ClosedConnectionState(ConnectionState):
@staticmethod
def read(conn):
raise RuntimeError("not open")

@staticmethod
def write(conn):
raise RuntimeError("not open")

@staticmethod
def open(conn):
conn.new_state(OpenConnectionState)

@staticmethod
def close(conn):
raise RuntimeError("already closed")

calss OpenConnectionState(ConnectionState):
@staicmethod
def read(conn):
print("reading")

@staticmethod
def write(conn, data):
print("writing", data)

@staticmethod
def open(conn):
raise RuntimeError("already open")

@staticmethod
def close(conn):
conn.new_state(ClosedConnectionState)

def test():
c = Connection()
c.open()
c.read()
c.write("hello")
c.close()

访问者模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
class Node:
pass

class UnaryOperator(Node):
def __init__(self, operand):
self.operand =operand

class BinaryOperator(Node):
def __init__(self, left, right):
self.left = left
self.right = right

class Add(BinaryOperator):
pass

class Sub(BinaryOperator):
pass

class Mul(BinaryOperator):
pass

class Div(BinaryOperator):
pass

class Nagate(UnaryOperator):
pass

class Number(Node):
def __init__(self, value):
self.value = value

class NodeVsistor:
def visit(self, node):
methname = "visit_" + type(node).__name__
meth = getattr(self, methname, None)
# 使用`getattr`获取相应方法,避免大量`switch`
# 子类需要实现`visit_Node`一系列方法
if meth is None:
meth = self.generic_visit
return meth(node)

def generic_visit(self, node):
raise RuntimeError("No {} method".format("visit_" + type(node).__name_))

class Evaluator(NodeVisitor):
def visit_Number(self, node):
return node.value

def visit_Add(self, node):
return self.visit(node.left) + self.visit(node.right)
# 递归调用`visit`计算结果
# 因此可能超过python递归嵌套层级限制而失败

def visit_Sub(self, node):
return self.visit(node.left) - self.visit(node.right)

def visit_Mul(self, node):
return self.visit(node.left) * self.visit(node.right)

def visit_Div(self, node):
return self.visit(node.left) / self.visit(node.right)

def visit_Negate(self, node):
return -node.operand

def test():
t1 = Sub(Number(3), Number(4))
t2 = Mul(Number(2), t1)
t3 = Div(t2, Number(5))
t4 = Add(Number(1), t3)

e = Evaluator()
e.visit(t4)

yield消除递归

消除递归一般是使用栈、队列,在python还可以使用yield得到 更加简洁的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import types

class NodeVisitor:
def visit(self, node):
stack = [node]
last_result = None
while stack:
try:
last = stack[-1]
if isinstance(last, types.GeneratorType):
# 对`yield`实现
stack.append(last.send(last_result))
last_result = None
elif isinstance(last, Node):
# 对递归实现
stack.append(self._visit(stack.pop()))
else:
last_result = stack.pop()
except StopIteration:
stack.pop()
return last_result

def _visit(self, node):
methname = "visit" + type(node).__name__
meth = getattr(self, methname, None)
if meth is None:
meth = self.generic_visit
return meth(node)

def generic_visit(self, node):
raise RuntimeError("No {} method".format("visit_", type(node).__name__))

class Evaluator(NodeVisitor):
# `yield`版本不会多次递归,可以接受更多层级
def visit_Number(self, node):
return node.value

def visit_Add(self, node):
yield (yield node.left) + (yield node.right)
# 遇到`yield`,生成器返回一个数据并暂时挂起

def visit_Sub(self, node):
yield (yield node.left) + (yield node.right)

def visit_Mul(self, node):
yield (yield node.left) * (yield node.right)

def visit_Div(self, node):
yield (yield node.left) * (yield node.right)

def visit_Nagate(self, node):
yield - (yield node.operand)

字符串调用方法

  • 可以使用getattr(instance, name)通过字符串调用方法
  • 也可以用operator.methodcaller
    • operator.methodcaller创建可调用对象,同时提供所有 必要参数
    • 调用时只需要将实例对象传递给其即可
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import math
from operator import methodcaller

class Point:
def __init__(self, x, y):
self.x = x
self.y = y

def __repr__(self):
return "Point({!r}, {!r})".format(self.x, self.y)

def distance(self, x, y):
return math.hypot(self.x -x, self.y - y)

def test():
points = [
Point(1, 2),
Point(3, 0),
Point(10, -3),
Point(-5, -7)
]

points.sort(key=methodcaller("distance', 0, 0))
# `methodcaller`创建可调用对象,并提供所有必要参数

缓存实例

工厂函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Spam:
def __init__(self, name):
self.name = name

import weakref
_spam_cache = weakref.WeakValueDictionary()
# `WeakValueDictionary`实例只保存在其他地方还被使用的
# 实例,否则从字典移除

def get_spam(name):
# 使用工厂函数修改实例创建行为
if name not in _spam_cache:
s = Spam(name)
_spam_cache[name] = s
else:
s = _spam_cache[name]
return s

def test():
a = get_spam("foo")
b = get_spam("foo")
print(a is b)

缓存管理器

将缓存代码放到单独的缓存管理器中,

  • 代码更清晰、灵活,可以增加更多的缓存管理机制
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import weakref

class CachedSpamManager:
def __init__(self):
self._cache = weakref.WeakValueDictionary()

def get_spam(self, name):
if name not in self._cache:
s = Spam(name)
self._cache[name] = s
else:
s = self._cache[name]
return s

def clear(self):
self._cache.clear()

class Spam:
manager = CacheSpamManager()
def __init__(self, *args, **kwargs):
# `__init__`方法抛出异常,防止用户直接初始化
# 也可以将类名加上`_`,提醒用户不要实例化
raise RuntimeError("can't instantiate directly")

@classmethod
def _new(cls, name):
self = cls.__new__(cls)
self.name = name
return self

__new__

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import weakref

clas Spam:
_spam_cache = weakref.WeakValueDictionary()

def __new__(cls, name):
if name in cls._spam_cache:
return cls._spam_cache[name]
else:
self = super().__new__(cls)
cls._spam_cache[name] = self
return self

def __init__(self, name):
print("initializing Spam")
self.name = name
# 这种方式实际会多次调用`__init__`方法,即使已经结果已经
# 缓存,不是个好方法