Lexical Analysis

  • python将读取的程序问题转换为Unicode码点
    • 源文件的文本编码可由编码声明指定
    • 默认UTF-8
  • 词法分析器将文件拆分为token
  • 解释器以词法分析器生成的token流作为输入

行结构

逻辑行

逻辑行:逻辑行的结束以NEWLINE token表示

  • 语句不能跨越逻辑行边集,除非其语法允许包含NEWLINE (如复合语句包含多个子语句)
  • 逻辑行可由一个、多个物理行按照明确、隐含行拼接规则 构成

python程序可以分为很多逻辑行

物理行

物理行:以行终止序列结束的字符序列

  • 源文件、字符串中可以使用任何标准平台上行终止序列
    • Unix:\n换行符LF
    • Win:\r\n回车加换行CR LF
    • Macintosh:\r回车CR
  • 输入结束会被作为最后物理行的隐含终止标志
  • 嵌入Python源码字符串应使用标准C传统换行符\n

显式行拼接

显式行拼接:两个、多个物理行使用\拼接为一个逻辑行

  • 物理行以不在字符串、注释内的反斜杠结尾时,将与下行 拼接构成一个单独逻辑行
    • 反斜杠、其后换行符将被删除
  • 以反斜杠结束的行不能带有注释
  • 反斜杠不能用于
    • 拼接注释
    • 拼接字符串外token
  • 不允许原文字符串以外反斜杠存在于物理行其他位置

隐式行拼接

隐式行拼接

  • 圆括号、方括号、花括号内表达式允许被分为多个物理行,无需 使用反斜杠

    • 拼接行可以带有注释
    • 后续行缩进不影响程序结构、允许为空白行
    • 拼接行之间不会有NEWLINE token
  • 三引号"""/'''字符串允许被分为多个物理行

    • 拼接行中不允许带有注释

空白行

空白行:只包含空格符、制表符、进纸符、注释的逻辑行会被忽略, 不生成NEWLINE token

  • 交互式输入语句时,对空白行处理可能因为读取-求值-打印循环 的具体实现而存在差异

    • 标准交互模式解释器中:完全空白逻辑行将会结束一条多行 复合语句

注释

注释:一不包含在字符串内的#开头,在物理行末尾结束

  • 注释标志逻辑行的结束,除非存在隐含行拼接规则
  • 注释在语法分析中被忽略,不属于token

编码声明

编码声明:位于python脚本第一、第二行,匹配正则表达式 coding[=:]\s*([-\w.]+)的注释将被作为编码声明处理

1
# -*- coding: <encoding-name> -*-
  • 表达式第一组指定了源码文件编码
    • 编码声明指定编码名称必须是python所认可的编码
    • 词法分析将使用此编码:语义字符串、注释、标识符
  • 编码声明须独占一行,若在第二行,则第一行也必须为注释
  • 没有编码声明,默认编码为UTF-8
    • 若文件首字节为UTF-8字节顺序标志b\xef\xbb\xbf, 文件编码也声明为UTF-8

缩进

缩进:逻辑行开头的空白(空格符、制表符)被用于计算该行 的缩进等级,决定语句段落组织结构

  • 首个非空白字符之前的空格总数确定该行的缩进层次
    • 缩进不能使用反斜杠进行拼接,首个反斜杠之前空格将确定 缩进层次
  • 制表符被替换为1-8个空白,使得缩进的空格总数为8倍数
    • 源文件若混合使用制表符、空格符缩进,并使得确定缩进 层次需要依赖于制表符对应空格数量设置,将引发 TabError
    • 由于非Unix平台上文本编辑器本身特性,源文件中混合使用 制表符、空格符是不明智的
  • 进纸符
    • 在行首时:在缩进层级计算中被忽略
    • 行首空格内其他位置:效果未定义,可能导致空格计数重置 为0
  • 不同平台可能会显式限制最大缩进层级

INDENT/DEDENT token

  • 读取文件第一行前,向堆栈中压入零值,不再弹出
  • 被压入栈的层级数值从底至顶持续增加
  • 每个逻辑行开头的行缩进层级将和栈顶进行比较
    • 相同:不做处理
    • 新行层级较高:压入栈中,生成INDENT token
    • 新行层级较低:应为栈中层级数值之一
      • 栈中高于该层级所有数值被弹出
      • 每弹出一级数值生成一个DEDENT token
  • 文件末尾,栈中剩余每个大于0数值生成一个DEDENT token

Tokens

型符

  • 空白字符不属于token
    • 除逻辑行开头、字符串内,空格符、制表符、进纸符等 空白符均可分隔token
    • 否则彼此相连的token会被解析为一个不同的token
  • 若存在二义性,将从左至右尽可能长读取合法字符串组成token

Indentifiers

标识符/名称:python标识符语法基于Unicode标准附件UAX-31,有 修改

  • ASCII字符集内可用于标识符与py2一致

    • 大、小写字母
    • 下划线_
    • 数字0-9
  • py3中引入ASCII字符集以外的额外字符

    • 其分类使用包含于unicodedata模块中Unicode字符数据库 版本
  • 标识符没有长度限制、大小写敏感

1
2
3
4
5
identifier   ::=  xid_start xid_continue*
id_start ::= <all characters in general categories Lu, Ll, Lt, Lm, Lo, Nl, the underscore, and characters with the Other_ID_Start property>
id_continue ::= <all characters in id_start, plus characters in the categories Mn, Mc, Nd, Pc and others with the Other_ID_Continue property>
xid_start ::= <all characters in id_start whose NFKC normalization is in "id_start xid_continue*">
xid_continue ::= <all characters in id_continue whose NFKC normalization is in "id_continue*">
  • Lu:大写字母
  • Ll:小写字母
  • Lt:词首大写字母
  • Lm:修饰字母
  • Lo:其他字母
  • Nl:字母数字
  • Mn:非空白标识
  • Mc:含空白标识
  • Nd:十进制数字
  • Pc:连接标点
  • Other_ID_Start:由PropList.txt定义的显式字符列表, 用来支持向后兼容
  • Other_ID_Continue:同上

Keywords

关键字:以下标识符作为语言的保留字、关键字,不能用作普通 标识符

1
2
3
4
5
6
7
False      await      else       import     pass
None break except in raise
True class finally is return
and continue for lambda try
as def from nonlocal while
assert del global not with
async elif if or yield

保留标识符类

以下划线字符开头、结尾的标识符类:具有特殊函数

  • _*:不会被from module import *导入

    • 特殊标识符_:交互式解释器中用于存放最近一次求值 结果,不处于交互模式时无特殊含义、无预定义
  • __*__:系统定义名称

    • 由解释器极其实现(包括标准库)定义
    • 任何不遵循文档指定方式使用__*__行为可能导致无警告 出错
  • __*:类私有名称

    • 在类定义中使用
    • 会以混合形式重写避免基类、派生类私有属性之间出现 名称冲突

字面值

字面值:表示一些内置类型常量

字符串、字节串字面值

1
2
3
4
5
6
7
8
9
10
stringliteral   ::=  [stringprefix](shortstring | longstring)
stringprefix ::= "r" | "u" | "R" | "U" | "f" | "F"
| "fr" | "Fr" | "fR" | "FR" | "rf" | "rF" | "Rf" | "RF"
shortstring ::= "'" shortstringitem* "'" | '"' shortstringitem* '"'
longstring ::= "'''" longstringitem* "'''" | '"""' longstringitem* '"""'
shortstringitem ::= shortstringchar | stringescapeseq
longstringitem ::= longstringchar | stringescapeseq
shortstringchar ::= <any source character except "\" or newline or the quote>
longstringchar ::= <any source character except "\">
stringescapeseq ::= "\" <any source character>
1
2
3
4
5
6
7
8
9
bytesliteral   ::=  bytesprefix(shortbytes | longbytes)
bytesprefix ::= "b" | "B" | "br" | "Br" | "bR" | "BR" | "rb" | "rB" | "Rb" | "RB"
shortbytes ::= "'" shortbytesitem* "'" | '"' shortbytesitem* '"'
longbytes ::= "'''" longbytesitem* "'''" | '"""' longbytesitem* '"""'
shortbytesitem ::= shortbyteschar | bytesescapeseq
longbytesitem ::= longbyteschar | bytesescapeseq
shortbyteschar ::= <any ASCII character except "\" or newline or the quote>
longbyteschar ::= <any ASCII character except "\">
bytesescapeseq ::= "\" <any ASCII character>
  • stringprefixbytesprefix与字面值剩余部分之间不允许 由空白符
  • 源字符集由编码声明定义
  • 字节串字面值只允许ASCII字符(但允许存储不大于256)
  • 两种字面值都可以使用成对(连续三个)单引号、双引号标示 首尾

    • 单引号'':允许包含双引号""
    • 双引号"":允许包含单引号''
    • 三重引号'''"""
      • 原样保留:未经转义的换行、(非三联)引号、空白符
  • 反斜杠\用于对特殊含义字符进行转义

字符串前缀
  • b/B前缀:字节串字面值

    • 创建bytes类型而非str类型实例
    • 只能包含ASCII字符
    • 字节对应数值大于128必须以转义形式表示
  • r/R:原始字符串/字节串

    • 其中反斜杠\被当作其本身字面字符处理
    • 转换序列不在有效
    • 原始字面值不能以单个\结束,会转义之后引号字符
  • f/F:格式化字符串字面值

转义规则
  • 字符串、字节串字面值中转义序列基本类似标准C转义规则

    • \xhh:必须接受2个16进制数码
  • 以下转义序列仅在字符串字面值中可用

    • \N{name}:Unicode数据库中名称为name的字符
    • \uxxxx:必须接受4个16进制数码
    • \Uxxxxxxxx:必须接受8个16进制数码
  • 无法识别的转义序列

    • py3.6之前将原样保留在字符串中
    • py3.6开始,将引发DeprecationWarning,未来可能会 引发SyntaxError
字符串字面值拼接
  • 多个相邻字符串、字符串字面值(空白符分隔),含义等同于 全部拼接为一体

    • 所用引号可以彼此不同(三引号风格也可用)
    • 每部分字符串可以分别加注释
    • 可以包括格式化字符串字面值
  • 此特性是在句法层面定义,在编译时实现

    • 在运行时拼接字符串表达式必须使用+运算符
格式化字符串字面值

格式化字符串字面值:带有f/F前缀的字符串字面值

  • 包含可替换字段,即以{}标示的格式表达式

    • 字符串{}以外部分按字面值处理
    • 双重花括号{ { } }被替换为相应单个花括号
  • 格式表达式被当作正常的包含在圆括号中python表达式处理 ,在运行时从左至右被求值

    • 不允许空表达式
    • lambda空表达式必须显式加上圆括号
    • 可以包含换行:如三引号字符串
    • 不能包含注释
    • 不能\反斜杠,考虑创建临时变量
  • 格式化字符串字面值可以拼接,但是一个替换字段不能拆分到 多个字面值中

  • 格式化字符串不能用作文档字符串,即使其中没有包含表达式

1
2
3
4
5
6
7
8
f_string          ::=  (literal_char | "{{" | "}}" | replacement_field)*
replacement_field ::= "{" f_expression ["!" conversion] [":" format_spec] "}"
f_expression ::= (conditional_expression | "*" or_expr)
("," conditional_expression | "," "*" or_expr)* [","]
| yield_expression
conversion ::= "s" | "r" | "a"
format_spec ::= (literal_char | NULL | replacement_field)*
literal_char ::= <any code point except "{", "}" or NULL>
  • !:标识转换字段

    • !s:对结果调用str()
    • !r:调用repr()
    • !a:调用ascii()
    1
    2
    3
    4
    name = "Fred"
    print(f"he said his name is {name!r}")
    print("he said his name is {repr(name)}")
    # 二者等价
  • ::标识格式说明符,结果使用format()协议格式化

    • 格式说明符被传入表达式或转换结果的.__format__() 方法
    • 省略格式说明符则传入空字符串
    1
    2
    3
    4
    5
    6
    7
    8
    9
    width, precision = 4, 10
    value = decimal.Deciaml("12.345")
    print(f"result: {value: {width}.{precision}}")

    today = datetime(yeat=2017, month=1, day=27)
    print(f"{today: %B %d, %Y}")

    number = 1024
    print(f"{number: #0x}")
  • 顶层格式说明符可以包含嵌套替换字段

    • 嵌套字段可以包含有自身的转换字段、格式说明符,但不能 包含更深层嵌套替换字段

数字字面值

  • 数字字面值不包括正负号
    • 负数实际上是由单目运算符-和字面值组合而成
  • 没有专门复数字面值
    • 复数以一对浮点数表示
    • 取值范围同浮点数
  • 数字字面值可以使用下划线_将数码分组提高可读性
    • 确定数字大小时,字面值之间的下滑线被忽略
    • 下划线可以放在数码之间、基数说明符0x等之后
    • 浮点数中不能直接放在.
整形数字字面值
  • 整形数字字面值没有长度限制,只受限于内存
1
2
3
4
5
6
7
8
9
10
integer      ::=  decinteger | bininteger | octinteger | hexinteger
decinteger ::= nonzerodigit (["_"] digit)* | "0"+ (["_"] "0")*
bininteger ::= "0" ("b" | "B") (["_"] bindigit)+
octinteger ::= "0" ("o" | "O") (["_"] octdigit)+
hexinteger ::= "0" ("x" | "X") (["_"] hexdigit)+
nonzerodigit ::= "1"..."9"
digit ::= "0"..."9"
bindigit ::= "0" | "1"
octdigit ::= "0"..."7"
hexdigit ::= digit | "a"..."f" | "A"..."F"
浮点数字字面值
  • 整形数部分、指数部分解析时总以10为计数
  • 浮点数字面值允许范围依赖于具体实现
1
2
3
4
5
6
floatnumber   ::=  pointfloat | exponentfloat
pointfloat ::= [digitpart] fraction | digitpart "."
exponentfloat ::= (digitpart | pointfloat) exponent
digitpart ::= digit (["_"] digit)*
fraction ::= "." digitpart
exponent ::= ("e" | "E") ["+" | "-"] digitpart
虚数字面值
  • 序数字面值将生成实部为0.0的复数
1
imagnumber ::=  (floatnumber | digitpart) ("j" | "J")

运算符

1
2
3
+       -       *       **      /       //      %      @
<< >> & | ^ ~
< > <= >= == !=

分隔符

1
2
3
4
(       )       [       ]       {       }
, : . ; @ = ->
+= -= *= /= //= %= @=
&= |= ^= >>= <<= **=
  • 以上列表中后半部分为增强赋值操作符
    • 在词法中作为分隔符,同时起运算作用
1
'       "       #       \
  • 以上可打印ASCII字符
    • 作为其他token组成部分时有特殊意义
    • 或对词法分析器有特殊意义
1
$       ?
  • 以上可打印ASCII字符不再Python词法中使用
    • 出现在字符串字面值、注释之外将无条件引发错误

Python执行模型

综述

Code Blocks

代码块:作为一个单元执行的一段python文本,代码块构成python 程序

  • 模块、函数体、类定义
  • 交互式输入的每条命令
  • 作为标准输入、命令行参数发送给解释器的脚本文件
  • 脚本命令
  • 传递给eval()exec()的字符串参数
  • 代码块在执行帧中执行,执行帧包含某些用于调试的管理信息 并决定代码块执行完成后操作

NamingBinding

Binding of Names

  • 名称:用于指代对象,通过名称绑定操作引入
  • 名称绑定方法

    • 传递参数
    • import语句
      • from ... import *会绑定被导入模块中定义的所有 公有名称(仅在模块层级上被使用)
    • 类、函数定义
    • 以标识符为目标的赋值
    • for循环开头、withexcept子句的as之后

    • del语句的目标也被视为绑定,虽然实际语义为解除名称 绑定

  • 变量类型

    • 局部变量:绑定在代码块中的名称,且未声明为nonlocal 、或global
    • 全局变量:绑定在模块层级的名称
      • 模块代码块中变量既为全局变量、也是局部变量
    • 自由变量:在代码块中使用但未在其中定义的变量
  • 自由变量不同于闭包变量,函数闭包变量__closure__要求 自由变量来自于父函数作用域

Scope

作用域:定义了代码块中名称的可见性

  • 名称的作用域包含

    • 定义该变量代码块
    • 定义变量代码块所包含的代码块 (除非被包含代码块中引入对该名称不同绑定)
  • 名称作用域虽然包括定义变量代码块所包含的代码块

    • 可以直接访问变量
    • 但修改变量必须使用globallocal等声明
  • 在代码块内任何位置进行名称绑定,则代码块内所有对该名称 的使用被认为是对代码块的引用

    • python没有声明语法,则代码块中局部变量可通过,在整个 代码块文本中扫描名称绑定来确定
  • 模块作用域在模块第一次被导入时创建

Resolution of Names

  • 若名称在代码块中被使用,会由包含它的最近作用域来解析

  • 若名称完全无法找到将raise NameError

  • 若当前作用域为函数作用域,且名称指向局部变量,若名称被 绑定值前被被使用将raise UnboundLocalErrorUnboundLocalErrorNameError子类)

  • (代码块)环境:对代码块可见的所作用域集合

global

  • global指定名称的使用是对最高层级命名空间中该名称 绑定的引用

    • 全局命名空间:包含代码块的模块命名空间
    • 内置命名空间:builtins模块的命名空间
  • global语句与同一代码块中名称绑定具有相同作用域

    • 若自由变量最近包含作用域中有global语句,其也会被 当作全局变量
  • global语句须在名称使用之前声明

nonlocal

  • nonlocal使得相应名称指向最近包含函数作用域的中绑定的 变量

  • 指定名称不存在于任何包含函数作用域则raise SyntaxError

内置命名空间

  • 与代码块执行相关联的内置命名空间实际上是通过其在全局命名 空间中搜索名称__builtins__找到,一般是字典、模块 (此时使用模块的命名空间字典)

  • 默认情况下

    • __main__模块中:__builtins__为内置模块builtins
    • __main__模块中:__buitlins__builtins模块 自身的字典别名

动态特性

  • 自由变量的名称解析

    1
    2
    3
    4
    5
    6
    i = 0
    def f():
    print(i)
    i = 42
    f()
    # 打印`42`
    • 发生在运行时,而不是编译时
    • 在全局命名空间中,而不是最近包含命名空间中

eval()exec()

  • eval()exec()没有对完整环境的访问权限来解析名称

    • 有可选参数用于重载全局、局部命名空间
    • 若只指定一个命名空间,则会同时作用于两者

  • 类中名称解析遵守大部分情况下同普通规则,但 未绑定局部变将在全局命名空间中搜索

  • 类定义的命名空间__dict__会成为类的属性字典

  • 类代码块中定义的名称的作用域会被限制在类代码块中,不会 扩展到方法代码块中,包括推导式、生成器表达式

    1
    2
    3
    class A:
    a = 42
    b = list(a + i for i in range(10))

Exception

异常:中断代码块的正常控制流程以便处理错误、其他异常条件的 方式

  • 在错误被检测到的位置引发

    • 解释器检测到运行时错误时错误
    • raise语句显式引发
  • 可被当前包围代码块、任何直接或间接主调代码块捕获、处理

    • try...except指定错误处理,finally子句清理代码
    • 异常通过实例标识、根据器类型选择执行except子句
  • 错误处理采用“终止”模型

    • 异常处理器可以找出发生的问题,在外层继续执行
    • 但不能修复错误的根源,并重试失败操作
    • 异常完全未被处理时,解释器会终止程序执行、或返回交互 模式循环,并打印栈回溯信息,除非为SystemExit异常
  • 异常实例可以携带关于异常状态的额外信息

    • 异常信息不是python API一部分,内容可能在不同python 版本间不经警告的改变

Simple Statements

简单语句:由单个逻辑行构成

  • 多条简单语句可以在同一物理行内、并以分号分隔
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
simple_stmt ::=  expression_stmt
| assert_stmt
| assignment_stmt
| augmented_assignment_stmt
| annotated_assignment_stmt
| pass_stmt
| del_stmt
| return_stmt
| yield_stmt
| raise_stmt
| break_stmt
| continue_stmt
| import_stmt
| future_stmt
| global_stmt
| nonlocal_stmt

Expression Statements

表达式语句:用于计算、写入值(交互模式下),或调用过程(不 返回有意义结果的函数)

1
expresssion_stmt ::= starred_expression
  • 用途:表达式语句对指定表达式[列表]进行求值
  • 交互模式下
    • 若值不为None:通过内置repr()函数转换为字符串, 单独一行写入标准输出
    • 值为None:不产生任何输出

Assignment Statements

赋值语句:将名称[重]绑定到特定值、修改属性或可变对象成员项

1
2
3
4
5
6
7
8
9
assignment_stmt ::=  (target_list "=")+ (starred_expression | yield_expression)
target_list ::= target ("," target)* [","]
target ::= identifier
| "(" [target_list] ")"
| "[" [target_list] "]"
| attributeref
| subscription
| slicing
| "*" target
  • 用途:对指定表达式列表求值,将单一结果对象从左至右逐个 赋值给目标列表

  • 赋值根据目标列表的格式递归定义,目标为可变对象组成部分时 (属性引用、抽取、切片),可变对象赋值有效性会被检查, 赋值操作不可接受可能引发异常

  • 赋值顺序:将赋值看作是左、右端项重叠

    • 根据定义赋值语句内多个赋值是同时重叠,如:a,b=b,a 交换两变量值
    • 但赋值语句左端项包含集合类型时,重叠从左到右依次执行

      1
      2
      3
      4
      x = [0,1]
      i = 0
      i, x[i] = 1, 2
      # `x`现在为`[0, 2]`
      • LOAD_XXX指令将右端项从左到右依次压栈
      • ROT_N指令交换栈顶元素
      • STORE_XXX指令将栈顶元素弹出从左到右依次给 右端项赋值
      • 以上语句中,计算表达式x[i]i已经被赋新值
      • dis.dis()查看代码块指令

赋值目标为列表

赋值目标(左端项)为列表(可选包含在圆括号、方括号内)

  • 若目标列表为不带逗号、可以包含在圆括号内的单一目标,将 右端项赋值给该目标

  • 否则:右端项须为与目标列表相同项数的可迭代对象,其中 元素将从左至右顺序被赋值给对应目标

  • 若目标列表包含带*元素:则类似实参解包,其须为可迭代 对象,且右端项至少包含目标列表项数-1

    • 加星目标前元素被右端项前段元素一一赋值
    • 加星目标后元素被右端项后段元素一一赋值
    • 加星目标被赋予剩余目标元素构成的列表

赋值目标为单个目标

目标为标识符(名称)

  • 名称未出现在当前代码块globalnonlocal语句中:名称 被绑定到/赋值为当前局部命名空间对象

  • 否则:名称被分别绑定到/赋值为全局命名空间、或nonlocal 确定的外层命名空间中对象

  • 若名称已经被绑定则被重新绑定,可能导致之前被绑定名称 对象引用计数变为0,对象进入释放过程并调用其析构器

目标为属性引用

  • 引用中原型表达式被求值:应产生具有可赋值属性的对象, 否则raise TypeError

  • 该对象指定属性将被赋值,若无法赋值将 raise AttributeError

目标为抽取项

  • 引用中原型表达式被求值:应产生可变序列对象(列表)、 映射对象(字典)

  • 引用中抽取表达式被求值

    • 若原型表达式求值为可变序列对象

      • 抽取表达式产生整数,包含负数则取模,结果只须为 小于序列长度非负整数
      • 整数指定的索引号的项将被赋值
      • 若索引超出范围将raise IndexError
    • 若原型表达式求值为映射对象

      • 抽取表达式须产生与该映射键类型兼容的类型
      • 映射可以创建、更新抽取表达式指定键值对
  • 对用户自定义对象,将调用__setitem__方法并附带适当参数

目标为切片

  • 引用中原型表达式被求值:应当产生可变序列对象

    • 右端项应当是相同类型的序列对象
  • 上界、下界表达式若存在将被求值

    • 其应为整数,若为负数将对原型表达式序列长度求模,最终 边界被裁剪至0、序列长度开区间中
    • 默认值分别为零、序列长度
  • 切片被赋值为右端项

    • 若切片长度和右端项长度不同,将在目标序列允许情况下 改变目标序列长度

Augmented Assignment Statements

增强赋值语句:在单个语句中将二元运算和赋值语句合为一体

1
2
3
4
augmented_assignment_stmt ::=  augtarget augop (expression_list | yield_expression)
augtarget ::= identifier | attributeref | subscription | slicing
augop ::= "+=" | "-=" | "*=" | "@=" | "/=" | "//=" | "%=" | "**="
| ">>=" | "<<=" | "&=" | "^=" | "|="
  • 增强赋值语句不能类似普通赋值语句为可迭代对象拆包
  • 赋值增强语句对目标和表达式列表求值

    • 依据两操作数类型指定执行二元运算
    • 将结果赋给原始目标
  • 增强赋值语句可以被改写成类似、但非完全等价的普通赋值语句

    • 增强赋值语句中目标仅会被求值一次
    • 在可能情况下,运算是原地执行的,即直接修改原对象而 不是创建新对象并赋值给原对象
    • 所以增强赋值先对左端项求值
  • 其他增强赋值语句和普通赋值语句不同点

    • 单条语句中对元组、多目标赋值赋值操作处理不同

Annotated Assignment Statements

带标注的赋值语句:在单个语句中将变量、或属性标注同可选赋值 赋值语句合并

1
annotated_assignment_stmt ::=  augtarget ":" expression ["=" expression]
  • 与普通赋值语句区别仅在于:仅有单个目标、且仅有单个右端项 才被允许

  • 在类、模块作用域中

    • 若赋值目标为简单名称,标注会被存入类、模块的 __annotations__属性中
    • 若赋值目标为表达式,标注被求值但不会被保存
  • 在函数作用域内,标注不会被求值、保存

  • 若存在右端项,带标注赋值在对标注值求值前执行实际赋值; 否则仅对赋值目标求值,不执行__setitem____setattr__

    • 参见cs_python/py3ref/#todo

关键字语句

assert

assert语句:在程序中插入调试性断言的简便方式

1
assert_stmt ::= "assert" expression ["," expression]
  • assert语句等价于

    1
    2
    3
    4
    5
    6
    7
    8
    9
    if __debug__:
    if not expression:
    raise AssertionError
    # 等价于`assert expression`

    if __debug__:
    if not expression1:
    raise AssertionError(expression2)
    # 等价于`assert expression1, expression2`
    • 无需再错误信息中包含失败表达式源码,其会被作为栈追踪 一部分被显示
  • 假定__debug__AssertionError指向具有特定名称的内置 变量,当前实现中

    • __debug__赋值是非法的,其值在解释器启动时确定
    • 默认内置变量__debug__=True
    • 请求优化时__debug__置为False
      • -0命令行参数开启
      • 若编译时请求优化,代码生成器不会为assert语句生成 代码

pass

pass语句:空操作,被执行时无事情发生

1
pass_stmt ::= "pass"
  • 适合语法上需要语句、但不需要执行代码时临时占位

del

del语句:从局部、全局命名空间中移除名称的绑定,若名称未绑定 将raise NameError

1
del_stmt ::= "del" target_list
  • 删除是递归定义的

    • 类似赋值的定义方式
    • 从左至右递归的删除目标列表中每个目标
  • 属性、抽取、切片的删除会被传递给相应原型对象

    • 删除切片基本等价于赋值为目标类型的空切片???

return

return语句:离开当前函数调用,返回列表表达式值、None

  • 在生成器函数中,return语句表示生成器已完成

    • raise StopIteration
    • 返回值作为参数构建StopIteration,并作为 StopIteration.value属性
  • 异步生成器函数中,return语句表示异步生成器已完成

    • raise StopAsyncIteration
    • 非空return返回值在将导致语法错误
1
return_stmt ::= "return" [expression_list]
  • return语法上只会出现于函数定义代码块中,不会出现于 类定义所嵌套代码中

  • 若提供表达式列表,其将被求值;否则缺省为None

  • return将控制流传出带有finally子句的try语句时, finally子句会先被执行然后真正离开函数

yield

yield语句:语义上等于yield表达式

1
yield_stmt ::= yield_expression
  • 可用于省略在使用等效yield表达式必须的圆括号

    1
    2
    3
    4
    5
    6
    yield <expr>
    yield from <expr>
    # 以上yield语句、以下yield表达式等价

    (yield <expr>)
    (yield from <expr>)
  • yeild表达式、语句仅在定义[异步]生成器函数时使用,且仅 用于函数体内部,且函数体包含yield就使得该定义创建 生成器函数而非普通函数

  • yield表达式参见cs_python/py3ref/#todo

raise

raise语句:引发异常

1
raise_stmt ::= "raise" [expression ["from" expression]]
  • 若不带表达式:raise将重新引发当前作用域最后一个激活 异常

    • 若当前作用域内没有激活异常,将引发RuntimeError 提示错误
  • 否则计算表达式作为异常对象

    • 异常对象须为BaseException子类、实例
    • 若其为类,则通过不带参数的实例化该类作为异常实例
  • 异常被引发时会自动创建回溯对象,并被关联至可写 __traceback__属性

    • 可以创建异常时同时使用.with_traceback()异常方法 自定义回溯对象

      1
      raise Exception("foo occured").with_traceback(tbobj)

异常串联

  • from子句用于异常串联:其后表达式求值需要为另一个异常

    • 其将作为可写__cause__属性被关联到引发的第一个异常
    • 若引发异常未被处理,两个异常都将被打印
  • 若异常在try语句中finally子句、其他子句后、先中引发, 类似机制发挥作用,之前异常被关联到新异常的__context__ 属性

  • 异常串联可以通过在from子句中指定None显式抑制

break

break语句:终结最近外层循环、循环的可选else子句

1
break_stmt ::= "break"
  • break在语法上只出现在forwhile所嵌套代码

    • 不包括循环内函数定义、类定义所嵌套代码
  • for循环被break终结,循环控制目标保持当前值

  • break将控制流传出带有finally子句的try语句时, finally子句会先被执行,然后真正离开循环

continue

continue语句:继续执行最近外层循环的下一伦茨

1
continue_stmt ::= "continue"
  • continue在语法上只出现在forwhile所嵌套代码

    • 不包括循环内函数定义、类定义、finally子句所嵌套 代码
  • continue将控制流传出带有finally子句的try语句时, finally子句会先被执行,然后真正开始循环下一个轮次

import

import语句

1
2
3
4
5
6
7
8
import_stmt     ::=  "import" module ["as" identifier] ("," module ["as" identifier])*
| "from" relative_module "import" identifier ["as" identifier]
("," identifier ["as" identifier])*
| "from" relative_module "import" "(" identifier ["as" identifier]
("," identifier ["as" identifier])* [","] ")"
| "from" module "import" "*"
module ::= (identifier ".")* identifier
relative_module ::= "."* module | "."+
  • 基本import子句执行步骤

    • 查找模块,若有必要加载并初始化模块
    • import所处作用域的局部命名空间定义名称
    • 语句包含多个子句(逗号分隔)时,以上两个步骤将分别 对每个子句执行,如同子句被分成独立import语句
  • 默认情况下,导入的父模块中命名空间中不包含子模块属性, 即导入父模块不能直接通过属性.引用子模块

    • 有些包会在父模块中导入子模块,则初始化模块时父模块 中即有子模块属性
    • 在当前模块手动导入子模块,子模块绑定至父模块命名空间 中同名属性
  • 导入机制参见cs_python/py3ref/import_system

绑定

  • 若模块名称后带有as,则在as之后名称将直接绑定到所导入 模块

  • 若没有指定其他名称、且被导入模块为最高层级模块,则模块 名称被绑定到局部命名空间作为对所导入模块的引用

  • 若被导入模块不是最高级模块,则包含该模块的最高层级包名将 被绑定到局部命名空间作为的该最高层级包的引用,所导入模块 必须使用完整限定名称访问而不能直接访问

from子句

  • 查找from子句中指定模块,若有必要则加载并初始化模块

  • import子句中指定的每个标识符

    • 检查被导入模块是否有该名称属性
    • 若没有,尝试导入具有该名称子模块,然后再次检查 被导入(上级)模块是否具有该属性
    • 若未找到该属性,则raise ImportError
    • 否则将对该值引用存入局部命名空间,若有as子句则 使用其指定名称,否则使用该属性名称
*通配符

标识符列表为通配符*形式:模块中定义的全部公有名称都被绑定 至import语句作用域对应局部命名空间

  • 模块命名空间中__all__属性:字符串列表,指定模块 定义的公有名称

    • 其中字符串项为模块中定义、导入的名称
    • 其中中所给出的名称被视为公有、应当存在
    • 应该包含所有公有API、避免意外导出不属于API部分项
  • __all__属性未定义:则公有名称集合将包括在模块 命名空间中找到的、所有不以_开头名称

  • 通配符模式仅在模块层级允许使用,在类、函数中使用将 raise SyntaxError
相对导入

相对导入:指定导入模块时,无需指定模块绝对名称

  • 需要导入的模块、包被包含在同一包中时,可在相同顶级包中 进行相对导入,无需指明包名称

  • from子句中指定的模块、包中使用前缀点号指明需要上溯 包层级数

    • 一个前缀点号:执行导入的模块在当前包
    • 两个前缀点号:上溯一个包层级
    • 三个前缀点号:上溯两个包层级,依此类推

      1
      form ...sub_sub_pkg import mod1
  • 相对导入可以避免模块之间产生冲突,适合导入相关性强代码

    • 脚本模式(在命令行中执行.py文件)不支持相对导入
    • 要跨越多个文件层级导入,只需要使用多个.,但 PEP 328建议,相对导入层级不要超过两层

future语句

future语句:指明莫格特定模块使用在特定、未来某个python发行版 中成为标准特性的语法、语义

1
2
3
4
5
future_stmt ::=  "from" "__future__" "import" feature ["as" identifier]
("," feature ["as" identifier])*
| "from" "__future__" "import" "(" feature ["as" identifier]
("," feature ["as" identifier])* [","] ")"
feature ::= identifier
  • import __future__ [as name]:不是future语句,只是没有 特殊语义、语法限制的普通import语句
  • 用途

    • 允许模块在包含新特性发行版前使用该特性
    • 目的是使得在迁移至引入不兼容改变的python未来版本 更容易
  • future语句是针对编译器的指令

    • 在编译时被识别并做特殊对待

      • 改变核心构造语义常通过生成不同代码实现
      • 新特性可能会引入不兼容语法,如:新关键字,编译器 可能需要以不同方式解析模块
    • 编译器需要知道哪些特性名称已经定义

      • 包含未知特性的future语句将引发编译时错误
    • 直接运行时语义同其他import语句

      • 相应运行时语义取决于future语句启用的指定特性
    • 在包含future语句的环境中,通过exec()compile() 调用代码会使用future语句关联的语法、语义,此行为 可以通过compile()可选参数加以控制
  • future语句必须在靠近模块开头位置处出现,可以出现在future 语句前的行

    • 模块文档字符串
    • 注释
    • 空行
    • 其他future语句

global

global语句:声明所列出标识符将被解读为全局变量

1
global_stmt ::= "global" identifier ("," identifier)*
  • global语句是作用于整个当前代码块的声明

  • 局部作用域中给全局变量赋值必须用到global关键字

    • 仅仅是获取值无需global语句声明
    • 但自由变量也可以指向全局变量而不必声明为全局变量
  • global语句中列出的名称

    • 不能被定义为形参名
    • 不能作为for循环控制目标
    • 不能出现在类定义、函数定义、import语句、变量标注中
    • CPython:暂时未强制要求上述限制,未来可能更改
  • global是对解释器的指令,仅对与global语句同时被解析 的代码起作用

    • 包含在作为exec()参数的字符串、代码对象中global 语句不会影响exec()所在代码块
    • 反之exec()中代码也不会被调用其代码块影响
    • eval()compile()等函数同

nonlocal

nonlocal语句:使得列出的名称指向之前最近的包含作用域中 绑定的、除全局变量外的变量

1
nonlocal_stmt ::= "nonlocal" indentifier ("," identifier)*
  • nonlocal语句允许被封装代码重新绑定局部作用域以外、且非 全局(模块)作用域当中变量

    • 即nonlocal语句中列出名称,必须指向之前存在于 包含作用域中的绑定

    • nonlocal语句中列出名称不能与之前存在的局部作用域中 绑定冲突

数据模型--函数决定对象

函数

用户定义函数

用户定义函数:通过函数定义创建,调用时附带参数列表

  • 函数对象支持获取、设置任意属性
    • 用于给函数附加元数据
    • 使用属性点号.获取、设置此类属性

特殊属性

  • __defaults__:有默认值参数的默认值组成元组
    • 没有具有默认值参数则为None
  • __code__编译后函数体代码对象
  • __globals__:存放函数中全局变量的字典的引用
    • 即引用函数所属模块的全局命名空间
    • 只读
  • __closure__:包含函数自由变量绑定单元的元组
    • 没有则为None
    • 只读
  • __annotations__:包含参数注释的字典
    • 字典键为参数名、return(若包含返回值)
    • 将变量名称(非私有)映射到标注值的特殊字典
    • 若该属性可写、在类或模块体开始执行时,若静态地发现 标注则被自动创建
  • __kwdefaults__keyword-only参数的默认值字典
  • 大部分可写属性会检查赋值类型
  • 自由变量:上层命名空间中变量,不包括顶级命名空间,即 全局变量不是自由变量

实例/绑定方法

实例/绑定方法:使用属性表示法调用定义在类命名空间 中的函数

  • 实例方法用于将类、类实例同可调用对象结合起来

    • 可调用对象:须为类属性,即定义在类命名空间中, 通常为用户定义函数、类方法对象
  • 通过实例访问类命名空间定义函数时创建实例/绑定方法

    • 通过示例、类访问的命名空间定义函数类型不同
      • wrapper_descriptor转换为method-wrapper
      • function转换为method
      • 通过类访问方法,得到是普通函数
    • 绑定:此时会将self作为首个参数添加到参数列表
    • 调用实例方法时,调用相应下层函数
  • 函数对象到实例方法对象的转换每次获取实例该属性时都会发生

    • 有时可将属性赋值给本地变量、调用实现性能优化
    • 非用户定义函数、不可调用对象在被获取时不会发生转换
    • 实例属性的用户定义函数不会被转换为绑定方法,仅在函数 是类的属性时才会发生
  • Py3中以上特性依赖于___getattribute__实现

属性

  • 绑定方法对象支持只读获取底层函数对象任意属性

    • 但方法属性实际保存在下层函数对象中
    • 所以不能直接设置绑定方法的方法属性,必须在下层函数 对象中显式设置,否则raise AttributeError
  • 绑定方法有两个特殊只读属性

    • m.__self__:操作该方法的类实例
    • m.__func__:底层实现该方法的函数
  • m(args,...)完全等价于m.__func__(m.__self__,args,...)

特殊元属性

  • __self__:类对象实例
  • __func__:函数对象实例
  • __doc__:方法文档,等同于__func__.__doc__
  • __name__:方法名,等同于__func__.__name__
  • __module__:定义方法所在模块名

Class Method Objects

类方法:提供了始终将类绑定为函数对象首个参数的方式

  • 对其他对象的封装,通常用于封装用户定义方法对象
  • 会改变从类、类实例获取该对象的方式
  • 用途
    • 实现自定义、多个构造器,如
      • 只调用__new__()、绕过__init__,创建未初始化 的实例
      • 反序列化对象:从字节串反序列构造符合要求的对象
  • 通过classmethod()构造器创建
  • Py3中以上特性依赖于___getattribute__实现

Static Method Objects

静态方法:提供了避免将函数对象转换为方法对象的方式

  • 对任意其他对象的封装,通常用于封装用户定义方法对象
  • 从类、类实例获取静态方法对象时,实际返回的是封装的对象, 不会被进一步转换
  • 静态方法对象自身不是可调用的,但其封装的对象通常可调用
  • 通过内置staticmethod()构造器创建
  • Py3中以上特性依赖于___getattribute__实现

内置函数、方法

  • 内置函数:对C函数的外部封装
  • 内置方法:内置函数另一种形式,(类似实例方法)隐式传入 当前实例作为C函数额外参数
  • 包括以下两种类型
    • builtin_function_or_method
    • wrapper_descriptor
  • 参数数量、类型由C函数决定
  • 内置方法由支持其的类型描述

特殊元属性

  • __self__<module 'builtins' (built-in)>
  • __doc__:函数/方法文档
  • __name__:函数/方法名
  • __module__:所在模块名

说明

  • 函数是描述器,函数类function实现有__get__方法
  • function.__get__即将函数首个参数进行绑定,返回绑定方法
  • 参见cs_python/py3ref/cls_special_methods

Generator Functions

生成器函数:使用yield语句的函数、方法称为生成器函数

  • 生成器函数调用时返回生成器迭代器对象,控制执行函数体
  • yield表达式参见cs_python/py3ref/expressions

Generator-Iterator

生成器迭代器:生成器函数执行得到的迭代器

  • 实现有迭代器协议__next__的迭代器类实例不同于 生成器迭代器,其仅实现迭代

    • 迭代执行过程即__next__函数调用,重入(状态维护) 由类负责
    • 无不同迭代状态区别
    • 不会自动获得.send.throw.close等方法
  • 或者说生成器迭代器是:利用yield表达式对迭代器的的简化 实现,并预定义.send.throw.close方法

  • 迭代器协议参见cs_python/py3ref/cls_special_methods

执行过程

  • 其某方法被调用时,生成器函数开始执行
  • 执行到第一个yield表达式被挂起,保留所有局部状态
    • 局部变量当前绑定
    • 指令指针
    • 内部求值栈
    • 任何异常处理状态
  • 返回expression_list
  • 调用生成器某方法,生成函数继续执行
  • 执行return、函数体执行完毕将raise StopIteration
  • 生成器表达式、yield表达式参见 cs_python/py3ref/expressions

生成器迭代器状态

生成器在声明周期中有如下4中状态

  • "GEN_CREATED":等待执行
  • "GEN_RUNNING":正在执行,只有多线程中才能看到
  • "GEN_SUSPENDED":在yield表达式处挂起状态
  • "GEN_CLOSED":关闭状态
  • 可通过inspect.getgeneratorstate()方法查看

__next__

1
2
def(pre) generator.__next__():
pass
  • 用途:开始生成器函数执行、或从上次执行yield表达式处恢复 执行

    • 生成器函数通过__next__方法恢复执行时,yield表达式 始终取值为None
    • 执行至下个yield表达式
  • 返回值:生成器迭代器产生的下个值

    • yield表达式中expression_list
    • 若生成器没有产生下个值就退出,raise StopIteration
  • 此方法通常隐式通过for循环、next()函数调用

send

1
2
def(pre) generator.send(value):
pass
  • 用途:恢复生成器函数执行并向其“发送”值value

    • value参数作为yield表达式的结果
    • 执行至下个yield表达式
  • 返回值:生成器迭代器产生的下个值

    • yield表达式中expression_list
    • 若生成器没有产生下个值就退出,raise StopIteration
  • 说明

    • 若生成器中包含子迭代器,send传参数值将被传递给 下层迭代器,若子迭代器没有合适接收方法、处理,将 raise AttributeErrorraise TypeError
    • .send方法参数为None时实际不会有传递参数行为
      • 调用.send()启动生成器时,必须以None作为调用 参数,因为此时没有可以接收值的yield表达式
      • 子迭代器中没有处理参数时,.send(None)也能正常 工作

throw

1
2
def(pre) generator.throw(type[, value[, traceback]]):
pass
  • 用途:在生成器暂停位置处引发type类型异常

    • 若异常被处理:则执行直至下个yield表达式
    • 若生成器函数没有捕获传入异常、或引发另一个异常:异常 会被传播给调用者
  • 返回值:返回生成器产生的下个值(若异常被处理)

    • 若生成器没有产生下个值就退出,raise StopIteration
  • 说明

    • 若生成器中包含子迭代器,throw传入异常将被传递给 子迭代器
    • 调用throw启动生成器,会在生成器函数开头引发错误

close

1
2
def(pre) generator.close():
pass
  • 用途:在生成器函数暂停处raise GeneratorExit
    • 若之后生成器函数正常退出、关闭、引发GeneratorExit (生成器中未捕获该异常):则关闭生成器并返回调用者
    • 若生成器继续产生值:则raise RuntimeError
    • 若生成器引发其他异常:则传播给调用者
    • 若生成器由于异常、或正常而退出:则无操作

Yield表达式—调用外部函数

  • 生成器函数执行yield表达式类似调用外部函数
  • 当前函数栈保留当前状态、挂起
  • 执行yield表达式“完毕”后,“返回”到调用处
  • yield表达式“返回值”取决于生成器恢复执行所调用方法
    • .__next__fornext()调用,返回None
    • .send():返回.send()参数
  • 此时整体类似于
    • 主调函数调用生成器函数
    • 生成器函数调用yield表达式

生成器函数—协程

  • 生成器函数类似协程,也被称为semi-coroutine,是协程子集
  • 相似点
    • yield多次,有多个入口点
    • 执行可以被挂起
    • 可在恢复执行时传递参数控制执行
  • 不同点
    • yield后控制权总是转移给生成器迭代器调用者,生成器 函数不能控制yield后继续执行的位置 (yield表达式仅仅传递值给父程序,并不是指定需要 跳转的、平等的协程)

生成器函数—try

  • try结构中任何位置都允许yield表达式
  • 若生成器在销毁之前没有恢复执行(引用计数为0、被垃圾 回收),try结构中的yield表达式挂起可能导致finally 子句执行失败

  • 此时应由运行该异步生成器的事件循环、或任务调度器负责调用 生成器-迭代器close()方法,从而允许任何挂起的finally 子句得以执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def echo(value=None):
print("execution start")
try:
while True:
try:
value = (yield value)
except Exception as e:
value = e
finally:
print("clean up when gen.close() is called")

if __name__ == "__main__":
gen = echo(1)
print(next(gen)) # 1
print(next(gen)) # None
print(gen.send(2)) # 2
print(gen.throw(TypeError, "spam")) # TypeError('spam',)
gen.close() # clean up...

Coroutine Function

协程函数:使用async def定义的函数、方法

  • 调用时返回一个coroutine对象
  • 可能包含await表达式、async withasync for语句
    • 即协程函数能在执行过程中挂起、恢复
  • 协程参见cs_program/#todo

Coroutine Objects

协程对象:属于awaitable对象

  • 协程执行:调用__await__,迭代其返回值进行控制

    • 协程结束执行、返回时,迭代器raise StopIteration, 异常的value属性将指向返回值
    • 等待协程超过一次将raise RuntimeError
  • 若协程引发异常,其会被迭代器传播

    • 协程不应该直接引发未处理的StopIteration
  • 可等待对象参见cs_python/py3ref/cls_special_methods

send

1
2
def(pre) coroutine.send(value):
pass
  • 用途:开始、恢复协程执行

    • value is None:相当于等待__await__()返回迭代器 结果下一项
    • value is not None:此方法将委托给导致协程挂起的 迭代器的send()方法
  • 返回值:返回值、异常等同对__await__()返回值迭代结果

throw

1
2
def(pre) coroutine.throw(type[, value[, traceback]]):
pass
  • 用途:在协程内引发指定异常

    • 此方法将委托给导致协程内挂起的迭代器的throw()方法 ,若其存在
    • 否则异常在挂起点被引发
  • 返回值:返回值、异常等同对__await__()返回值迭代结果

    • 若异常未在协程内被捕获,则传回给主调者

close

1
2
def(pre) coroutine.close():
pass
  • 用途:清理自身并退出
    • 若协程被挂起,此方法先被委托给导致该协程挂起的迭代器 的.close()方法,若其存在
    • 然后在挂起点raise GeneratorExit,使得协程立即清理 自身
    • 最后协程被标记为已结束执行,即使未被启动
    • 协程对象将被销毁时,会被自动调用

Asynchronous Generator Functions

异步生成器函数:包含yield语句、使用async def定义函数、 方法(即在协程中)

  • 返回异步生成器迭代器对象,控制执行函数体
  • yield from表达式在异步生成器函数中使用将引发语法错误

Asynchronous Generator-Iterator

异步生成器迭代器

  • 异步体现:async def定义协程函数中可以使用异步代码

  • 异步生成器迭代器方法返回的可等待对象执行同一函数栈

    • 可等待对象被await运行时才会执行异步函数体
    • 类似普通生成器迭代器,执行到yield表达式挂起
    • 则连续返回多个可等待对象可以乱序await
    • 可以视为返回待执行函数体
  • 异步生成器迭代器执行过程、yield表达式、try结构、同异步 迭代器协议关联, 均参见普通生成器迭代器

  • 异步迭代器协议参见cs_python/py3ref/cls_special_methods

  • 异步生成器函数使用yield from语句将引发语法错误

最终化处理#todo

处理最终化:事件循环应该定义终结器函数

  • 其接收异步生成器迭代器、且可能调用aclose()方法并执行 协程
  • 终结器可以通过调用sys.set_asyncgen_hooks()注册
  • 首次迭代时,异步生成器迭代器将保存已注册终结器以便最终化 时调用

__anext__

1
2
async def(pre) coroutine agen.__anext__():
pass
  • 用途:返回可等待对象,可等待对象运行时开始、恢复异步 生成器函数执行

    • 异步生成器函数通过__anext__方法恢复执行时,返回的 可等待对象中yield表达式始终取值为None
  • 可等待对象返回值:返回异步生成器的下个值

    • 执行至下个yield表达式,返回expression_list
    • 若异步生成器没有产生下个值就退出,则 raise StopAsyncIteration
  • 此方法通常由async for异步循环隐式调用

asend

1
2
async def(pre) coroutine agen.asend(value):
pass
  • 用途:返回可等待对象,可等待对象运行时开始、恢复异步 生成器函数执行,并向其“发送”值value

    • value参数作为yield表达式的结果
    • 执行至下个yield表达式
  • 返回值:异步生成器产生的下个值

    • yield表达式中expression_list
    • 若生成器没有产生下个值就退出,则 raise StopAsyncIteration
  • 说明

    • 调用.asend()启动生成器时,必须以None作为调用参数

athrow

1
2
async def(pre) coroutine agen.athrow(type[, value[, traceback]]):
pass
  • 用途:返回可等待对象,可等待对象运行时将在异步生成器函数 暂停位置处引发type类型异常

    • 若异常被处理:则执行直至下个yield表达式
    • 若异步生成器函数没有捕获传入异常、或引发另一个异常: 可等待对象运行时异常会被传播给调用者
  • 返回值:返回生成器产生的下个值(若异常被处理)

    • 若生成器没有产生下个值就退出,则 raise StopAsyncIteration
  • 说明

    • 调用athrow启动异步生成器,会在函数开头引发错误

aclose

1
2
async def(pre) coroutine agen.aclose():
pass
  • 用途:返回可等待对象,可等待对象运行时在异步生成器函数 暂停处raise GeneratorExit
    • 若异步生成器函数正常退出、关闭、引发GeneratorExit (生成器中未捕获该异常):则运行可等待对象将 raise StopIteration???
    • 后续调用异步生成器迭代器方法返回其他可等待对象:则 运行可等待对象将raise StopAsyncIteration
    • 若异步生成器函数产生值:则运行可等待对象将 raise RuntimeError
    • 若异步生成器迭代器已经异常、正常退出,则后续调用 aclose方法将返回无行为可等待对象

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中对数据的抽象

  • 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)