Python类用法实例

延迟计算

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

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

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

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

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

import math

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

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

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

数据模型类型约束

使用描述器在对实例某些属性赋值时进行检查

类继承方案

基础构建模块

创建数据模型、类型系统的基础构建模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Descriptor:
def __init__(self, name=None, **opts):
self.name = name
for key, value in opts.items()
setattr(self, key, value)

def __set__(self, instance, value):
if instance is None:
return self
instance.__dict__[self.name] = value

class Typed(Descriptor):
def __set__(self, instance, value):
if value < 0:
raise ValueError("expected >= 0")
super().__set__(instance, value)

class Unsigned(Descriptor):
def __set__(self, instance, value):
if value < 0:
raise ValueError("expect >= 0")
super().__set__(instance, value)

class MaxSized(Descriptor):
def __init__(self, name=None, **opts):
if "size" not in opts:
raise TypeError("missing size options")
super.__init__(name, **opts)

def __set__(self, instance, value):
if len(value) >= self.size:
raise ValueError("size must be <" + str(self.size))
super().__set__(instance, value)

具体数据类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Integer(Typed):
expected_type = int

class UsignedInteger(Integer, Unsigned):
# 描述器类是基于混入实现的
pass

class Float(Typed):
expected_type = Float

class UnsignedFloat(Float, Unsigned):
pass

class String(Typed):
expected_type = str

class SizedString(String, MaxSized):
pass

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
class Stock:
name = SizedString("name", size=8)
shares = UnsignedInteger("shares")
price = UnsignedFloat("price")

def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price

def check_attributes(**kwargs):
def decrator(cls):
for key, value in kwargs.items():
if isinstance(value, Descriptor):
value.name = key
setattr(cls, key, value)
else:
setattr(cls, key, value(key))
return cls
return decrator

@check_attributes(name=SizedString(size=8),
shares=UnsignedInteger,
price=UnsignedFloat)
# 使用类装饰器简化版本
class Stock2:
def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price

class checkmeta(type):
def __new__(cls, clsname, bases, methods):
for key, value in method.items():
if isinstance(value, Descriptor):
value.name = key
return type.__new__(cls, clsname, bases, methods)

class Stock3(metaclass=checkdmeta):
name = SizedString(size=8)
shares = UnsignedInteger()
price = UnsignedFloat()

def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price

装饰器类替代mixin

使用类装饰器、元类都可以简化代码,但类装饰器更加灵活

  • 类装饰器不依赖其他任何新技术
  • 类装饰器可以容易的添加、删除
  • 类装饰器能做为mixin的替代技术实现同样的效果,而且速度 更快,设置一个简单的类型属性值,装饰器要快一倍

示例1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
def LoggedMapping(cls):
cls_getitem = cls.__getitem__
cls_setitem = cls.__setitem__
cls_delitem = cls.__setitem__
# 获取原`cls`的方法,避免死循环调用

def __getitem__(self, key):
print("getting", str(key))
return cls_getitem(self, key)
# 这里使用之前获取的方法指针调用,而不直接使用
# `cls.__getitem__`避免死循环

def __setitem__(self, key, value):
pritn("setting {} = {!r}", str(key))
return cls_set(self, key, value)

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

cls.__getitem__ = __getitem__
cls.__setitem__ = __setitem__
cls.__delitem__ = __delitem__

return cls

@LoggedMapping
class LoggedDict(dict):
pass

示例2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
def Type(expected_type, cls=None):
if cls is None:
return lambda cls: Typerd(expected_type, cls)
super_set = cls.__set__

def __set__(self, instance, value):
if no instance(value, expected_type):
raise TypeError("expect " + str(expected_type))
super_set(self, instance, value)

cls.__set__ = __set__
return cls

def Unsigned(cls):
super_set = cls.__set__

def __set__(self, instance, value):
if value < 0:
raise TypeError("missing size option")
super_set(self, name, **opts)

cls.__init__ = __set__
return cls

def MaxSized(cls):
super_init = cls.__init__

def __init__(self, name=None, **opts):
if "size" not in opts:
raise TypeError("missing size option")
super_init(self, name, **opts)

cls.__init__ = __init__

super_set = cls.__set__

def __set__(self, instance, value):
if len(value) >= self.size:
raise ValueError("size must be <" + str(self.size))
super_set(self, instance, value)

cls.__set__ = __set__
return cls

@Typed(int)
class Integer(Descriptor):
pass

@Unsigned
class UnsignedInteger(Integer):
pass

@Typed(float)
class Float(Descriptor):
pass

@Unsigned
class UnsignedFloat(Float):
pass

@Typed(str)
class String(Descriptor):
pass

@MaxSized
class SizedString(String):
pass

自定义容器

collections定义了很多抽象基类,可以用于定义自定义基类

collections.Sequence

Sequence需要实现的抽象方法有:

  • __getitem__
  • __len__
  • add

继承自其的类,支持的常用操作:索引、迭代、包含判断、切片

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

class SortedItems(Sequence):
# 必须要实现所有抽象方法,否则报错
def __init__(self, initial=None):
self._items = sorted(initial) if initial is not None else [ ]
def __getitem__(self, index):
return self._items[index]

def __len__(self):
return len(self._items)

def add(self, item):
bisect.insort(self._items, item)
# `bisect`模块是用于在排序列表中高效插入元素
# 保证在元素插入之后仍然保持顺序

# `SortedItems`继承了`colllections.Sequence`,现在和
# 普通序列无差,支持常用操作:索引、迭代、包含判断、切片

def test():
items = SortedItems([5, 1, 3])
print(list(items))
print(items[0], items[-1])
items.add(2)
print(list(items))

if instance(items, collections.Iterable):
pass
if instance(items, collections.Sequence):
pass
if instance(items, collections.Container):
pass
if instance(items, collections.Sized):
pass
if instance(items, collections.Mapping):
pass

collections.MutableSequence

MutableSequence基类包括需要实现的抽象方法

  • __getitem__
  • __setitem__
  • __delitem__
  • __len__
  • insert

提供的可用方法包括;

  • append
  • count:统计某值出现的次数
  • remove:移除某值的元素
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
from collections import MutableSequence

class Item(collections.MutableSequence):
def __init__(self, initial=None):
self._items = list(initial) if initial is not None else [ ]

def __getitem__(self, index):
print("getting:", index)
return self._items[index]

def __setitem__(self, index):
print("setting:", index)
self._items[index] = value

def __delitem__(self, index):
print("deleting:", index)
del self._items[index]

def insert(self, index, value):
print("inserting:", index, value)
self._items.insert(index, value)

def __len__(self):
print("len")
return len(self._items)

# 基本支持几乎所有的核心列表方法:`append`、`remove`、
# `count`

def count():
a = Items([1, 2, 3])
print(len(a))
a.append(4)
# 在末尾添加元素
# 调用了`__len__`、`insert`方法
a.count(2)
# 统计值为`2`出现的次数
# 调用`__getitem__`方法
a.remove(3)
# 删除值为`3`的元素
# 调用`__getitem__`、`__delitem__`方法

属性的代理访问

代理是一种编程模式,将某个操作转移给另一个对象来实现

  • 需要代理多个方法时,可以使用__getattr__

    • __getattr__方法只在属性、方法不存在时才被调用, 所以代理类实例本身有该属性不会触发该方法,也不会代理 至被代理类

    • 如果需要管理所有对方法、属性的访问,可以定义 __getattribute__,其在对类的所有属性、访问时均会 被触发,且优先级高于__getattr__

    • __setattr____delattr__需要约定是对代理类还是 被代理类操作,通常约定只代理_开头的属性,即代理类 只暴露被代理类公共属性

    • 注意:对象的元信息直接访问能通过__getattr__代理, 但是对应的hook可能无法正常工作,如果需要,要单独为 代理类实现元信息代理方法

  • 通过自定义属性访问方法,可以用不同方式自定义代理类行为, 如:日志功能、只读访问

  • 代理有时候可以作为继承的替代方案:代理类相当于继承了被 代理类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
class Proxy:
# 这个类用于包装、代理其他类,修改其行为
def __init__(self, obj):
self._obj = obj

def __getattr__(self, name):
print("getattr:", name)
return getattr(self._obj, name)

def __setattr__(self, name, value):
if name.startwith("_"):
# 约定只代理不以`_`开头的属性
# 代理类只暴露被代理类的公共属性
super().__setattr__(name, value)
else:
print("setattr:", name, value)
setattr(self._obj, name, value)

def __delattr__(self, name):
if name.startwith("_"):
super().__delattr__("name")
else:
print("delattr:", name)
delattr(self._obj, name)

class Spam:
def __init__(self, x):
self.x = x

def bar(self, x):
print("Spam.bar", self.x, y)

def test():
s = Spam(2)
p = Proxy(s)
p.bar(3)
p.x = 37
# 通过`p`代理`s`

p = Porxy([1, 3, 5])
# len(p)
# `len`函数直接使用会报错
p.__len__()
# `p.__len__`可以正常代理,返回代理的列表长度
# 这说明python中的钩子函数有特殊的结构?

状态机(状态模式)

为不同的状态定义对象,而不是使用过多的条件判断

  • 提高执行效率
  • 提高代码可维护性、可读性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
class Connection:
def __init__(self):
self.new_state(ClosedConnectionState)

def new_state(self, newstate):
self._state = newstate

def read(self):
return self._state.read(self)

def write(self, data):
return self._state.write(self, data)

def open(self):
return self._state.close(self)

class ConnectionState:
@staticmethod
def read(conn):
raise NotImplementedError()

@staticmethod
def write(conn, data):
raise NotImplementedError()

@staticmethod
def open(conn):
raise NotImplementedError()

@staticmethod
def close(conn):
raise NotImplementedError()

class ClosedConnectionState(ConnectionState):
@staticmethod
def read(conn):
raise RuntimeError("not open")

@staticmethod
def write(conn):
raise RuntimeError("not open")

@staticmethod
def open(conn):
conn.new_state(OpenConnectionState)

@staticmethod
def close(conn):
raise RuntimeError("already closed")

calss OpenConnectionState(ConnectionState):
@staicmethod
def read(conn):
print("reading")

@staticmethod
def write(conn, data):
print("writing", data)

@staticmethod
def open(conn):
raise RuntimeError("already open")

@staticmethod
def close(conn):
conn.new_state(ClosedConnectionState)

def test():
c = Connection()
c.open()
c.read()
c.write("hello")
c.close()

访问者模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
class Node:
pass

class UnaryOperator(Node):
def __init__(self, operand):
self.operand =operand

class BinaryOperator(Node):
def __init__(self, left, right):
self.left = left
self.right = right

class Add(BinaryOperator):
pass

class Sub(BinaryOperator):
pass

class Mul(BinaryOperator):
pass

class Div(BinaryOperator):
pass

class Nagate(UnaryOperator):
pass

class Number(Node):
def __init__(self, value):
self.value = value

class NodeVsistor:
def visit(self, node):
methname = "visit_" + type(node).__name__
meth = getattr(self, methname, None)
# 使用`getattr`获取相应方法,避免大量`switch`
# 子类需要实现`visit_Node`一系列方法
if meth is None:
meth = self.generic_visit
return meth(node)

def generic_visit(self, node):
raise RuntimeError("No {} method".format("visit_" + type(node).__name_))

class Evaluator(NodeVisitor):
def visit_Number(self, node):
return node.value

def visit_Add(self, node):
return self.visit(node.left) + self.visit(node.right)
# 递归调用`visit`计算结果
# 因此可能超过python递归嵌套层级限制而失败

def visit_Sub(self, node):
return self.visit(node.left) - self.visit(node.right)

def visit_Mul(self, node):
return self.visit(node.left) * self.visit(node.right)

def visit_Div(self, node):
return self.visit(node.left) / self.visit(node.right)

def visit_Negate(self, node):
return -node.operand

def test():
t1 = Sub(Number(3), Number(4))
t2 = Mul(Number(2), t1)
t3 = Div(t2, Number(5))
t4 = Add(Number(1), t3)

e = Evaluator()
e.visit(t4)

yield消除递归

消除递归一般是使用栈、队列,在python还可以使用yield得到 更加简洁的代码

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

class NodeVisitor:
def visit(self, node):
stack = [node]
last_result = None
while stack:
try:
last = stack[-1]
if isinstance(last, types.GeneratorType):
# 对`yield`实现
stack.append(last.send(last_result))
last_result = None
elif isinstance(last, Node):
# 对递归实现
stack.append(self._visit(stack.pop()))
else:
last_result = stack.pop()
except StopIteration:
stack.pop()
return last_result

def _visit(self, node):
methname = "visit" + type(node).__name__
meth = getattr(self, methname, None)
if meth is None:
meth = self.generic_visit
return meth(node)

def generic_visit(self, node):
raise RuntimeError("No {} method".format("visit_", type(node).__name__))

class Evaluator(NodeVisitor):
# `yield`版本不会多次递归,可以接受更多层级
def visit_Number(self, node):
return node.value

def visit_Add(self, node):
yield (yield node.left) + (yield node.right)
# 遇到`yield`,生成器返回一个数据并暂时挂起

def visit_Sub(self, node):
yield (yield node.left) + (yield node.right)

def visit_Mul(self, node):
yield (yield node.left) * (yield node.right)

def visit_Div(self, node):
yield (yield node.left) * (yield node.right)

def visit_Nagate(self, node):
yield - (yield node.operand)

字符串调用方法

  • 可以使用getattr(instance, name)通过字符串调用方法
  • 也可以用operator.methodcaller
    • operator.methodcaller创建可调用对象,同时提供所有 必要参数
    • 调用时只需要将实例对象传递给其即可
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import math
from operator import methodcaller

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

def __repr__(self):
return "Point({!r}, {!r})".format(self.x, self.y)

def distance(self, x, y):
return math.hypot(self.x -x, self.y - y)

def test():
points = [
Point(1, 2),
Point(3, 0),
Point(10, -3),
Point(-5, -7)
]

points.sort(key=methodcaller("distance', 0, 0))
# `methodcaller`创建可调用对象,并提供所有必要参数

缓存实例

工厂函数

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

import weakref
_spam_cache = weakref.WeakValueDictionary()
# `WeakValueDictionary`实例只保存在其他地方还被使用的
# 实例,否则从字典移除

def get_spam(name):
# 使用工厂函数修改实例创建行为
if name not in _spam_cache:
s = Spam(name)
_spam_cache[name] = s
else:
s = _spam_cache[name]
return s

def test():
a = get_spam("foo")
b = get_spam("foo")
print(a is b)

缓存管理器

将缓存代码放到单独的缓存管理器中,

  • 代码更清晰、灵活,可以增加更多的缓存管理机制
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import weakref

class CachedSpamManager:
def __init__(self):
self._cache = weakref.WeakValueDictionary()

def get_spam(self, name):
if name not in self._cache:
s = Spam(name)
self._cache[name] = s
else:
s = self._cache[name]
return s

def clear(self):
self._cache.clear()

class Spam:
manager = CacheSpamManager()
def __init__(self, *args, **kwargs):
# `__init__`方法抛出异常,防止用户直接初始化
# 也可以将类名加上`_`,提醒用户不要实例化
raise RuntimeError("can't instantiate directly")

@classmethod
def _new(cls, name):
self = cls.__new__(cls)
self.name = name
return self

__new__

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import weakref

clas Spam:
_spam_cache = weakref.WeakValueDictionary()

def __new__(cls, name):
if name in cls._spam_cache:
return cls._spam_cache[name]
else:
self = super().__new__(cls)
cls._spam_cache[name] = self
return self

def __init__(self, name):
print("initializing Spam")
self.name = name
# 这种方式实际会多次调用`__init__`方法,即使已经结果已经
# 缓存,不是个好方法

数据模型--执行相关

  • 以下类型为内部类型,由解释器内部使用、但被暴露给用户, 其定义可能随着未来解释器版本更新而变化

    • 代码对象
    • 帧对象
    • 回溯对象
    • 切片对象:参见cs_python/py3ref/dm_basics
    • 静态方法对象:参见cs_python/py3ref/#todo
    • 类方法对象:参见cs_python/py3ref/#todo

Module

模块:python代码的基本组织单元

  • 导入系统创建
    • import语句
    • importlibd.import_module()__import__()函数
  • 模块对象具有由字典__dict__实现的命名空间
    • 属性引用:被转换为该字典中查找m.__dict__['x']
    • 属性赋值:更新模块命名字典空间
    • 不包含用于初始化模块的代码对象
    • 模块中定义函数__globals__属性引用其

元属性

  • __name__:模块名称
  • __doc__:模块文档字符串
  • __annotaion__:包含变量标注的字典
    • 在模块体执行时获取
  • __file__:模块对应的被加载文件的路径名
    • 若加载自一个文件,某些类型模块可能没有
      • C模块静态链接至解释器内部
    • 从共享库动态加载的扩展模块,该属性为共享库文件路径名
  • __dict__:以字典对象表示的模块命名空间
  • CPython:由于CPython清理模块字典的设定,模块离开作用域时 模块字典将被清理,即使字典还有活动引用,可以复制该字典、 保持模块状态以直接使用其字典

Code Object

代码对象:“伪编译”为字节的可执行python代码,也称bytecode

  • 代码对象和函数对象区别

    • 代码对象不包含上下文;函数对象包含对函数全局对象 (函数所属模块)的显式引用
    • 默认参数值存放于函数对象而不是代码对象
    • 代码对象不可变,也不包含对可变对象的应用
  • 代码对象由内置compile()函数返回

    • 可以通过函数对象__code__属性从中提取
    • 可以作为参数传给exec()eval()函数执行

特殊属性

  • co_name:函数名称
  • co_argcount:位置参数数量
  • co_nlocals:函数使用的本地变量数量(包括参数)
  • co_varnames:包含本地变量名称的元组
  • co_freevars:包含自由变量的元组
  • co_code:表示字节码指令序列的字符串
  • co_consts:包含字节码所使用的字面值元组
    • 若代码对象表示一个函数,第一项为函数文档字符,没有 则为None
  • co_names:包含字节码所使用的名称的元组
  • co_filenames:被编译代码所在文件名
  • co_firstlineno:函数首行行号
  • co_lnotab:以编码表示的字节码偏移量到行号映射的字符串
  • co_stacksize:要求栈大小(包括本地变量)
  • co_flags:以编码表示的多个解释器所用标志的整形数
    • 0x04位:函数使用*arguments接受任意数量位置参数
    • 0x08位:函数使用**keywords接受任意数量关键字参数
    • 0x20位:函数是生成器
    • 0x2000位:函数编译时使用启用未来除法特性
    • 其他位被保留为内部使用

Frame Objects

栈帧对象:执行帧

  • 可能出现在回溯对象中,还会被传递给注册跟踪函数

特殊只读属性

  • f_back:前一帧对象,指向主调函数
    • 最底层堆栈帧则为None
  • f_code:此栈帧中所执行的代码对象
  • f_locals:查找本地变量的字典
  • f_globals:查找全局变量
  • f_builtins:查找内置名称
  • f_lasti:精确指令,代码对象字节码字符串的索引

特殊可写属性

  • f_traceNone,或代码执行期间调用各类事件的函数
    • 通常每行新源码触发一个事件
  • f_trace_lines:设置是否每行新源码触发一个事件
  • f_trace_opcodes:设置是否允许按操作码请求事件
  • f_lineno:帧当前行号
    • 可以通过写入f_lineno实现Jump命令

方法

  • .clear():清楚该帧持有的全部对本地变量的引用
    • 若该栈帧为属于生成器,生成器被完成
    • 有助于打破包含帧对象的循环引用
    • 若帧当前正在执行则会raise RuntimeError

Traceback Objects

回溯对象:表示异常的栈跟踪记录

  • 异常被印发时会自动创建回溯对象,并将其关联到异常的可写 __traceback__属性

    • 查找异常句柄使得执行栈展开时,会在每个展开层级的当前 回溯之前插入回溯对象
    • 进入异常句柄时,栈跟踪将对程序启用
    • 获取:sys.exc_info()返回的元组第三项、异常的 __traceback__属性
    • 程序没有合适的处理句柄时,栈跟踪将写入标准错误
  • 可通过types.TracebackType显式创建

    • 由回溯对象创建者决定如何链接tb_next属性构成完整 栈追踪

特殊只读属性

  • tb_frame:执行当前层级的执行栈帧
  • tb_lineno:给出发生异常所在行号
  • tb_lasti:最后具体指令
  • 若异常出现在没有匹配的except子句、没有finally子句 的try中,回溯对象中的行号、最后指令可能于相应帧对象中 行号不同

特殊可写属性

  • tb_next:栈跟踪中下一层级(通往发生异常的帧),没有 下一层级则为None

I/O对象/文件对象

文件对象:表示打开的文件

  • 创建文件对象

    • open()内置函数
    • os.popen()os.fdopen()
    • socket.makefile()
  • sys.stdinsys.stdoutsys.stderr会初始化为对应于 解释器的标准输入、输出、错误流对象

    • 均以文本模式打开
    • 遵循io.TextIOBase抽象类所定义接口

Compound Statements

复合语句

复合语句:包含其他语句(语句组)的语句

  • 复合语句由一个、多个子句组成,子句包含句头、句体

    • 子句头
      • 都处于相同的缩进层级
      • 以作为唯一标识的关键字开始、冒号结束
    • 子句体
      • 在子句头冒号后、与其同处一行的一条或多条分号分隔 的多条简单语句
      • 或者是在其之后缩进的一行、多行语句,此形式才能 包含嵌套的复合语句
  • 其会以某种方式影响、控制所包含的其他语句执行

1
2
3
4
5
6
7
8
9
10
11
12
13
compound_stmt ::=  if_stmt
| while_stmt
| for_stmt
| try_stmt
| with_stmt
| funcdef
| classdef
| async_with_stmt
| async_for_stmt
| async_funcdef
suite ::= stmt_list NEWLINE | NEWLINE INDENT statement+ DEDENT
statement ::= stmt_list NEWLINE | compound_stmt
stmt_list ::= simple_stmt (";" simple_stmt)* [";"]
  • 语句总以NEWLINE结束,之后可能跟随DEDENT
  • 可选的后续子句总是以不能作为语句开头的关键字作为开头, 不会产生歧义

关键字

if

if语句:有条件的执行

1
2
3
if_stmt ::=  "if" expression ":" suite
("elif" expression ":" suite)*
["else" ":" suite]
  • 对表达式逐个求值直至找到真值,在子句体中选择唯一匹配者 执行
  • 若所有表达式均为假值,则else子句体如果存在被执行

while

while语句:在表达式保持为真的情况下重复执行

1
2
while_stmt ::=  "while" expression ":" suite
["else" ":" suite]
  • 重复检验表达式
    • 若为真,则执行第1个子句体
    • 若为假,则else子句体存在就被执行并终止循环
  • 第1个子句体中break语句执行将终止循环,且不执行else 子句体
  • 第1个子句体中continue语句执行将跳过子句体中剩余部分, 直接检验表达式

for

for语句:对序列(字符串、元组、列表)或其他可迭代对象中元素 进行迭代

1
2
for_stmt ::= "for" target_list "in" expression_list ":" suite
["else" : suite]
  • 表达式列表被求值一次

    • 应该产生可迭代对象
    • python将为其结果创建可迭代对象创建迭代器
  • 迭代器每项会按照标准赋值规则被依次赋值给目标列表

    • 为迭代器每项执行依次子句体
    • 所有项被耗尽raise StopIteration时,else子句体 存在则会被执行
  • 目标列表中名称在循环结束后不会被删除

    • 但若序列为空,其不会被赋值
  • 序列在循环子句体中被修改可能导致问题

    • 序列的__iter__方法默认实现依赖内部计数器和序列长度 的比较
    • 若在子句体中增、删元素会使得内部计数器“错误工作”
    • 可以对整个序列使用切片创建临时副本避免此问题
  • 第1个子句体中break语句执行将终止循环,且不执行else 子句体
  • 第1个子句体中continue语句执行将跳过子句体中剩余部分, 转至下个迭代项执行

try

try语句:为一组语句指定异常处理器、清理代码

1
2
3
4
5
6
7
try_stmt  ::=  try1_stmt | try2_stmt
try1_stmt ::= "try" ":" suite
("except" [expression ["as" identifier]] ":" suite)+
["else" ":" suite]
["finally" ":" suite]
try2_stmt ::= "try" ":" suite
"finally" ":" suite

except子句

except子句:指定一个、多个异常处理器

  • try子句中没有异常时,没有异常处理器执行

  • 否则,依次检查except子句直至找到和异常匹配的子句

    • 无表达式子句必须是最后一个,将匹配任何异常
    • 有表达式子句中表达式被求值,求值结果同异常兼容则匹配 成功
      • 若在表达式求值引发异常,则对原异常处理器搜索取消
      • 其被视为整个try语句引发异常,将在周边代码、 主调栈中为新异常启动搜索
    • 若无法找到匹配的异常子句,则在周边代码、主调栈中继续 搜索异常处理器
    • 兼容:是异常对象所属类、基类,或包含兼容异常对象元组
  • 当找到匹配except子句时

    • 异常将被赋值给as子句后目标,若存在as子句
    • 对应子句体被执行(所有except子句都需要子句体)
    • as后目标在except子句结束后被清除

      1
      2
      3
      4
      5
      6
      7
      8
      except E as N:
      foo
      # 被转写为
      except E as N:
      try:
      foo
      finally:
      del N
      • 避免因异常附加回溯信息而形成栈帧的循环引用,使得 所有局部变量存活直至下次垃圾回收
      • 则异常必须赋值给其他名称才能在except子句后继续 引用
  • except子句体执行前,有关异常信息存放在sys模块中, 参见cs_python/py3std/os_sys.md

else子句

else子句:在以下情况将被执行,若存在

  • 控制流离开try子句体没有引发异常
  • 没有执行returncontinuebreak语句

finally子句

finally子句:指定清理处理器,子句体在任何情况下都被执行

  • 执行期间程序不能获取任何异常信息

    • tryexceptelse子句中引发的任何未处理异常 将被临时保存,执行完finally子句后被重新引发

    • 但若finally子句中执行returnbreak语句,则临时 保存异常被丢弃

    • finally子句引发新的异常,临时保存异常作为新异常 上下文被串联

    • 显式异常串联参见cs_python/py3ref/simple_stmt
  • try子句中执行returnbreakcontinue语句时, finally子句在控制流离开try语句前被执行

    • 函数返回值由最后被执行return语句决定,而 finally子句总是最后被执行

      1
      2
      3
      4
      5
      6
      7
      8
      def foo():
      try:
      return "try"
      finally:
      return "finally"

      foo()
      # 返回"finally"

with

with语句:包装上下文管理器定义方法中代码块的执行

1
2
with_stmt ::=  "with" with_item ("," with_item)* ":" suite
with_item ::= expression ["as" target]
  • with句头中有多个项目,被视为多个with语句嵌套处理多个 上下文管理器

    1
    2
    3
    4
    5
    6
    with A() as a, B() as b:
    suite
    # 等价于
    with A() as a:
    wiht B() as b:
    suite

执行流程

  • 对表达式求值获得上下文管理器
  • 载入上下文管理器__exit__以便后续使用
  • 调用上下文管理器__enter__方法
  • 若包含as子句,__enter__返回值将被赋值给其后目标
    • with语句保证若__enter__方法返回时未发生错误, __exit__总会被执行
    • 若在对目标列表赋值期间发生错误,视为在语句体内部发生 错误
  • 执行with语句体
  • 调用上下文关管理器__exit__方法
    • 若语句体退出由异常导致
      • 其类型、值、回溯信息将被作为参数传递给__exit__ 方法;否则提供三个None作为参数
      • __exit__返回值为假,该异常被重新引发;否则 异常被抑制,继续执行with之后语句
    • 若语句体由于异常以外任何原因退出
      • __exit__返回值被忽略

Function

函数定义:对用户自定义函数的定义

1
2
3
4
5
6
7
8
9
10
11
12
funcdef                 ::=  [decorators] "def" funcname "(" [parameter_list] ")"
["->" expression] ":" suite
decorators ::= decorator+
decorator ::= "@" dotted_name ["(" [argument_list [","]] ")"] NEWLINE
dotted_name ::= identifier ("." identifier)*
parameter_list ::= defparameter ("," defparameter)* ["," [parameter_list_starargs]]
| parameter_list_starargs
parameter_list_starargs ::= "*" [parameter] ("," defparameter)* ["," ["**" parameter [","]]]
| "**" parameter [","]
parameter ::= identifier [":" expression]
defparameter ::= parameter ["=" expression]
funcname ::= identifier
  • 函数定义是可执行语句

    • 在当前局部命名空间中将函数名称绑定至函数对象(函数 可执行代码包装器)
    • 函数对象包含对当前全局命名空间的引用以便调用时使用
  • 函数定义不执行函数体,仅函数被调用时才会被执行

Decorators

装饰器:函数定义可以被一个、多个装饰器表达式包装

  • 函数被定义时将在包含该函数定义作用域中对装饰器表达式求值 ,求值结果须为可调用对象
  • 其将以该函数对象作为唯一参数被调用;返回值将被绑定至函数 名称
  • 多个装饰器会以嵌套方式被应用
1
2
3
4
5
6
7
8
@f1(arg)
@f2
def func():
pass
# 大致等价,仅以上不会临时绑定函数对象至名称
def func():
pass
func = f1(arg)(f2(func))

Parameter Types

形参类型

  • POSITIONAL_OR_KEYWORD:之前没有VAR_POSITIONAL类型的 参数

    • 可以通过位置关键字传值
  • KEYWORD_ONLY:之前存在VAR_POSITION类型、或*的参数

    • 只能通过关键字传值
  • VAR_POSITIONAL*args形式参数

    • 只能通过位置传值
    • 隐式默认值为()
  • VAR_KEYWORD**kwargs形式参数

    • 只能通过关键字传值
    • 隐式默认值为{}
  • POSITIONAL_ONLY:只能通过位置传值的参数

    • 某些实现可能提供的函数包含没有名称的位置参数
    • 唯一不能使用关键字传参参数类型
    • CPython:C编写、PyArg_ParseTuple()解析参数的函数

Default Parameters Values

默认参数值:具有parameter = expression形式的形参

  • 具有默认值的形参,对应argument可以在调用中可被省略

  • 默认形参值将在执行函数定义时按从左至右的顺序被求值

    • 即函数定义时的预计算值将在每次调用时被使用
    • 则被作为默认值的列表、字典等可变对象将被所有未指定该 参数调用共享,应该避免
      • 可以设置默认值为None,并在函数体中显式测试
  • POSITION_OR_KEYWORD、有默认值形参必须位于无默认值者后 ,即若形参具有默认值,后续所有在*前形参必须具有默认值
  • KEYWORD_ONLY、有默认值形参可位于无默认值者前

Annotations

  • 形参标注:param:expression
  • 函数返回标注:-> expression
  • 标注不会改变函数语义
  • 标注可以是任何有效python表达式
    • 默认在执行函数定义时被求值
    • 使用future表达式from __future__ import annotations ,则标注在运行时被保存为字符串以启用延迟求值特性
  • 标注默认存储在函数对象__annotation__属性字典中
    • 可以通过对应参数名称、"return"访问

Class

类定义:对类的定义

1
2
3
classdef    ::=  [decorators] "class" classname [inheritance] ":" suite
inheritance ::= "(" [argument_list] ")"
classname ::= identifier
  • 类定义为可执行语句

    • 继承列表inheritance通常给出基类列表、元类
    • 基类列表中每项都应当被求值为运行派生子类的类
    • 没有继承类列表的类默认继承自基类object
  • 类定义语句执行过程

    • 类体将在新的执行帧中被执行
    • 使用新创建的局部命名空间和原有的全局命名空间
    • 类体执行完毕之后
      • 丢弃执行帧
      • 保留局部命名空间
    • 创建类对象
      • 给定继承列表作为基类
      • 保留的局部命名空间作为属性字典__dict__
    • 类名称将在原有的全局命名空间中绑定至该类对象
  • 类可以类似函数一样被装饰

    • 装饰器表达式求值规则同函数装饰器
    • 结果被绑定至类名称
  • 类中属性、方法参见#todo
  • 类属性可以作为实例属性的默认值,但注意使用可变类型值可能 导致未预期结果

Coroutine

协程函数

1
2
async_funcdef ::=  [decorators] "async" "def" funcname "(" [parameter_list] ")"
["->" expression] ":" suite
  • 协程函数可以在多个位置上挂起(保存局部状态)、恢复执行
  • 协程函数体内部
    • awaitasync是保留关键字
    • await表达式、async forasync with只能在协程 函数体内部使用
    • 使用yield from表达式将raise SyntaxError

async for语句

1
async_for_stmt ::= "async" for_stmt
  • async for语句允许方便的对异步迭代器进行迭代

    1
    2
    3
    4
    async for TARGET in ITER:
    ...BLOCK1...
    else:
    ...BLOCK2...

    在语义上等价于

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    iter = (ITER)
    iter = type(iter).__aiter__(iter)
    running = True
    while running:
    try:
    TARGET = await type(iter).__anext__(iter)
    except StopAsyncIteration:
    running = False
    else:
    BLOCK1
    else:
    BLOCK2

async with语句

1
async_with_stmt ::= "async" with_stmt
  • async with语句允许方便使用异步上下文管理器

    1
    2
    async with EXPR as VAR:
    BLOCK

    语义上等价于

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    mgf = (EXPR)
    aexit = type(mgr).__aexit__
    aenter = type(mgr).__aenter__(mgr)

    VAR = await aenter
    try:
    BLOCK
    except:
    if not await aexit(mgr, *sys.exc_info()):
    raise
    else:
    await aexit(mgr, None, None, None)