函数把一项计算或操作封装为可以重复调用的对象。阅读一个函数时,先看它接受什么参数、返回什么结果、是否改变外部对象,再看实现。内置函数也遵守各自的调用约定;名字出现在速查表里,不等于任意对象都能传给它。
本文示例以 Python 3.11 为基线。每段 Python 代码可独立保存并运行;其中的 assert 用于检查结果,运行时不要使用会禁用断言的 -O。类型注解的作用另见 类型注解与 typing。
定义、调用与返回值
跳转到“定义、调用与返回值”def 执行时创建函数对象并绑定名字;函数体通常到调用时才执行。return 把值交给调用者,没有显式返回值时结果为 None。print() 则输出文本,它不能替代需要继续计算的返回值。
参数既可以限制位置,也可以限制关键字。下面用一个小例子观察绑定结果,而不是只记住星号的形状。
def combine(first, /, second=0, *extra, scale=1, **labels): """Combine numeric inputs and return their labels separately.""" total = (first + second + sum(extra)) * scale return total, labels
assert combine(2) == (2, {})assert combine(2, second=3, scale=10) == (50, {})assert combine(2, 3, 4, 5, scale=2, unit="ms") == (28, {"unit": "ms"})args = (2, 3, 4)options = {"scale": 2, "unit": "ms"}assert combine(*args, **options) == (18, {"unit": "ms"})
# A spelling equal to a positional-only parameter can still occur in **labels.assert combine(2, first="label") == (2, {"first": "label"})try: combine(first=2)except TypeError: pass # This keyword did not supply the required positional argument.else: raise AssertionError("missing positional argument was accepted")
def report_only(value): print(value)
assert report_only("done") is Noneprint("parameter and return checks passed")这里 / 前的 first 只能按位置提供;second 可以按位置或关键字提供;extra 收集其后的额外位置参数;scale 只能按关键字提供;labels 收集剩余关键字。调用端的 *args、**options 是展开,定义端则是在说明如何接收。重复给同一个普通参数赋值、遗漏必需参数或展开非字符串关键字键,都会报错。函数参数
默认值、可变对象与固定次数循环
跳转到“默认值、可变对象与固定次数循环”默认值在执行函数定义时求值,不是在每次调用时重新求值。因此,需要每次调用都得到新列表时,可以使用 None 作为默认标记,再在函数体内创建列表。传入已有列表时,是否修改它应成为明确约定。
def append_value(value, bucket=None): if bucket is None: bucket = [] bucket.append(value) return bucket
first = append_value(1)second = append_value(2)assert first == [1] and second == [2] and first is not secondshared = [10]assert append_value(20, shared) is sharedassert shared == [10, 20]
def constant(n: int) -> int: """Run a fixed number of iterations; n is deliberately unused.""" count = 0 for _ in range(100_000): count += 1 return count
assert constant(0) == constant(1_000_000) == 100_000print("default-value and fixed-loop checks passed")原笔记的 constant(n) 保留在这里,用来说明复杂度的参照对象:循环次数固定,与 n 无关,所以相对于 n 是常数阶。这不代表执行时间为零,也不代表这个未使用参数的接口适合真实业务。n: int 与 -> int 本身不会在调用时强制类型检查;函数注解语法和后来的 typing 类型工具也不是同一项版本特性。
名字重新绑定与修改对象内容的区别,见 作用域与名字绑定;需要独立对象时,还应区分 浅拷贝与深拷贝。
内置函数按任务查找
跳转到“内置函数按任务查找”下表保留原清单中的全部名称,按用途组织。int、list、property 等本身是类型,也可以像函数一样调用;不能把所有可调用名称都理解成同一种普通函数。
| 用途 | 原清单名称 | 使用时检查什么 |
|---|---|---|
| 数值与进制 | abs()、divmod()、pow()、round()、bin()、oct()、hex() | 参数是否支持对应数值协议;round 的中点规则与浮点误差 |
| 构造与转换 | bool()、int()、float()、complex()、str()、list()、tuple()、dict()、set()、frozenset() | 类型转换可能失败;可变性和元素要求各不相同 |
| 字节与字符 | bytes()、bytearray()、memoryview()、chr()、ord() | 编码、整数范围、缓冲区是否共享、Unicode 码点 |
| 迭代与选择 | iter()、next()、enumerate()、zip()、map()、filter()、reversed() | 返回迭代器还是容器、何时耗尽、长度不一致如何处理 |
| 汇总与排列 | len()、all()、any()、sum()、min()、max()、sorted() | 空输入、真值规则、比较方式与返回值 |
| 范围与切片 | range()、slice() | 停止值不包含在内;slice 描述索引规则,本身不是可迭代序列 |
| 表示与格式 | repr()、ascii()、format() | 面向调试还是展示;ascii 会转义非 ASCII 字符;格式规格由对象解释 |
| 类型与身份 | type()、object()、isinstance()、issubclass()、id()、hash()、callable() | 继承关系、可哈希性、对象生存期;可调用不保证某组参数能调用成功 |
| 属性查询与修改 | getattr()、setattr()、delattr()、hasattr()、dir()、vars() | 属性访问可能执行描述器或钩子;并非每个对象都有 __dict__ |
| 类与方法工具 | classmethod()、staticmethod()、property()、super() | 绑定对象、属性描述器和方法解析顺序,见 类与继承 |
| 命名空间与代码 | globals()、locals()、compile()、eval()、exec() | 代码模式、显式命名空间;不要依赖修改 locals 来重绑定函数局部变量 |
| 输入、输出与帮助 | input()、print()、open()、help() | 文本输入与转换分开、文件生命周期、交互式帮助的使用环境 |
内置函数的完整版本清单见 Python 3.11 内置函数。上表保留的是这篇原笔记涉及的名称,不声称已经列出该版本所有内置名称。
迭代器、空输入和切片
跳转到“迭代器、空输入和切片”map()、filter()、zip() 等通常按需产生结果,转成 list 后才在这里完整求值。迭代器耗尽后不会自动从头开始;需要再次遍历时,应保留原容器并重新调用 iter()。
values = [0, 1, 2, 3]assert list(map(lambda x: x * 2, values)) == [0, 2, 4, 6]assert list(filter(lambda x: x > 1, values)) == [2, 3]assert list(enumerate("ab", start=1)) == [(1, "a"), (2, "b")]assert list(zip([1, 2], ["a"])) == [(1, "a")]try: list(zip([1, 2], ["a"], strict=True))except ValueError: passelse: raise AssertionError("strict zip accepted unequal lengths")
iterator = iter([10])assert next(iterator) == 10assert next(iterator, "end") == "end"assert list(iterator) == []assert list(reversed(values)) == [3, 2, 1, 0]assert sorted([3, 1, 2]) == [1, 2, 3]assert values == [0, 1, 2, 3]assert all([]) is True and any([]) is Falseassert sum([]) == 0assert min([], default=None) is Noneassert max(values) == 3 and len(values) == 4assert list(range(1, 6, 2)) == [1, 3, 5]selection = slice(2, 5, 2)assert "abcdefg"[selection] == "ce"try: list(selection)except TypeError: passelse: raise AssertionError("slice unexpectedly became an iterable")print("iteration checks passed")这也修正了原表中 list(slice(...)) 的例子:切片要交给支持索引的对象使用。sorted() 返回新列表;对于列表原地排序应查 list.sort()。all([]) 为真、any([]) 为假是定义明确的空输入结果,不应当作数据已经通过了某个业务完整性检查。
数值、文本与共享字节缓冲区
跳转到“数值、文本与共享字节缓冲区”chr() 和 ord() 对应 Unicode 码点,不限于 ASCII。round() 遇到两个同样近的候选值时,内置数值类型通常取偶数一侧;二进制浮点表示还可能影响看似处于十进制中点的值。
assert abs(-10) == 10assert divmod(10, 3) == (3, 1)assert pow(2, 3) == 8assert (bin(13), oct(13), hex(13)) == ("0b1101", "0o15", "0xd")assert round(2.5) == 2 and round(3.5) == 4assert round(2.675, 2) == 2.67assert chr(0x4E2D) == "中" and ord("中") == 0x4E2Dassert ascii("中") == "'\\u4e2d'"assert format(123, "08") == "00000123"assert repr("a\nb") == "'a\\nb'"
data = bytearray([65, 66, 67])view = memoryview(data)view[1] = 90assert data == bytearray(b"AZC")snapshot = bytes(data)view.release()data[0] = 88assert snapshot == b"AZC" and data == bytearray(b"XZC")assert bool([]) is False and bool([0]) is Trueassert int("10", 16) == 16assert float("3.5") == 3.5assert complex(2, 3) == 2 + 3jassert str(12) == "12"assert tuple("ab") == ("a", "b")assert list("ab") == ["a", "b"]assert dict(key="value") == {"key": "value"}assert set([1, 1, 2]) == {1, 2}assert frozenset([1, 1, 2]) == frozenset({1, 2})print("numeric, text and buffer checks passed")memoryview 直接观察支持缓冲区协议的对象;上面的赋值改动了原 bytearray。bytes(data) 创建的是此时的不可变字节值,之后修改原 bytearray 不会改变它。这里释放视图也让缓冲区的使用边界明确。
属性、身份与可调用对象
跳转到“属性、身份与可调用对象”id() 在对象存活期间唯一且稳定;对象销毁后该数值可能复用。将它称为“内存地址”是特定解释器的实现描述,不能当成 Python 程序跨实现依赖的接口。hash() 用于哈希容器,不能代替持久 ID;字符串的哈希值也不应被当作跨进程稳定结果。
class Item: category = "sample"
def __init__(self, value): self.value = value
def __call__(self, offset): return self.value + offset
item = Item(10)alias = itemassert item is alias and id(item) == id(alias)assert type(item) is Itemassert isinstance(item, Item) and issubclass(Item, object)assert callable(item) and item(2) == 12try: item()except TypeError: passelse: raise AssertionError("callable() unexpectedly checked required arguments")
assert getattr(item, "value") == 10assert getattr(item, "missing", None) is Nonesetattr(item, "label", "A")assert hasattr(item, "label") and "label" in dir(item)assert vars(item)["label"] == "A"delattr(item, "label")assert not hasattr(item, "label")assert vars(Item)["category"] == "sample"assert hash((1, 2)) == hash((1, 2))try: hash([])except TypeError: passelse: raise AssertionError("mutable list unexpectedly became hashable")assert object() is not object()print("attribute and callable checks passed")hasattr() 会实际尝试属性访问,并按是否抛出 AttributeError 判断,因此不能把它理解成完全没有副作用的名字查询。dir() 方便交互式探索,也不保证列出对象在所有动态情况下可能提供的每个属性。自定义属性钩子和 __call__ 的协议细节见 特殊方法。
globals、locals 与显式执行环境
跳转到“globals、locals 与显式执行环境”globals() 返回当前模块的全局命名空间字典。locals() 在这里用于读取函数局部变量;Python 3.11 下不要依赖修改它来改变函数实际局部绑定。确实要执行动态代码时,显式提供并读取命名空间字典。
下面只执行程序中固定的示例字符串。eval、exec 不是解析任意外部输入的通用数据接口;去掉几个内置名称也不构成安全隔离。
module_value = 10assert globals()["module_value"] == 10
def inspect_local(): local_value = 20 return locals()["local_value"]
assert inspect_local() == 20exec('print("Hello, World!")')namespace = {"greeting": "Hello"}code = compile( "def greet(name):\n" " return greeting + ', ' + name\n" "def add(a, b):\n" " return a + b\n" "result = add(3, 5)\n", "<example>", "exec",)assert exec(code, namespace) is Noneassert namespace["greet"]("Alice") == "Hello, Alice"assert namespace["result"] == 8assert eval(compile("x + y", "<expression>", "eval"), {"x": 2, "y": 3}) == 5
global_space = {"greeting": "Hello"}local_space = {}exec("message = greeting + ', Bob'", global_space, local_space)assert local_space["message"] == "Hello, Bob"exec("value = 7\ndef read_value():\n return value\n", global_space, local_space)assert local_space["value"] == 7try: local_space["read_value"]()except NameError: pass # The function resolves value in global_space, not local_space.else: raise AssertionError("separate exec namespaces were conflated")global_space["value"] = 9assert local_space["read_value"]() == 9print("explicit-namespace checks passed")同一个字典同时用作全局与局部环境时,赋值结果和新定义函数使用的全局环境保持一致。分开两个字典时,顶层赋值可落在局部字典,而函数体中的全局查询使用全局字典,这正是上面 read_value() 先失败后返回 9 的原因。需要输出结果时读回显式字典,比假定 exec() 一定替调用函数创建了可直接访问的新局部变量更清楚。动态代码执行
compile() 的 "eval" 模式用于表达式,"exec" 模式用于语句组;编译得到代码对象还没有执行其中的操作。处理 JSON 等数据时使用对应数据格式工具,而不是把数据改成 Python 源码再执行。
输入输出与继续阅读
跳转到“输入输出与继续阅读”input() 返回文本,读到输入结束时可能抛出 EOFError;需要整数时再显式转换并处理错误。open() 的文本模式应明确编码,并用 with 限定文件使用时间;详见 文件、路径与进程环境。help() 适合在交互式解释器中查询对象签名和说明,例如 help(print),不必把整份帮助输出写进业务日志。
原笔记中的 super() 五类用法——调用重写方法、初始化、多个基类、转交参数和访问继承属性——集中到 类与协作继承,其中会区分方法解析顺序与“简单从左到右依次调用所有父类”的错误理解。