Python函数编程核心:从参数传递到装饰器与生成器实战 1. 从“工具”到“思维”为什么函数是Python编程的基石刚接触Python那会儿我总觉得函数就是个“打包代码”的工具把几行常用的逻辑塞进去起个名字下次调用省事。直到后来项目越做越复杂一个脚本动辄上千行我才真正体会到函数远不止于此——它是你构建清晰、健壮、可维护程序的核心思维模型。今天我们就抛开那些蜻蜓点水的教程深入聊聊Python函数里那些真正影响你代码质量的细节。无论你是刚入门的新手还是已经写过一些脚本但总觉得代码“乱糟糟”的开发者这篇文章都会带你重新认识这个最基础、也最重要的概念。我们会从最底层的原理讲起一直聊到高级用法和实战避坑指南目标只有一个让你写的函数不仅能用而且好用、耐看。2. 函数基础不止于def和return2.1 定义与调用的本质一个命名空间的故事很多人学函数第一步就是def my_func():然后return something。这没错但如果我们只停留在这就错过了理解函数威力的第一扇门。当你写下def时你实际上在做两件事第一你创建了一个新的、临时的局部命名空间第二你定义了一个在当前命名空间中可用的名字函数名它指向一段可执行的代码对象。def calculate_area(width, height): 计算矩形面积 area width * height # area是局部命名空间内的变量 return area result calculate_area(5, 3) print(result) # 输出: 15 # print(area) # 这里会报错NameError因为area只在函数内部存在这个“局部命名空间”机制是函数实现封装和信息隐藏的基础。函数内部的变量如area与外部世界隔离这避免了命名冲突也让你的逻辑更纯粹。调用函数calculate_area(5, 3)时Python会做一系列操作为这次调用创建一个新的栈帧stack frame将参数5和3分别绑定到局部变量width和height上然后在这个干净的沙箱里执行代码最后将返回值传递出来并销毁这个临时沙箱。注意这里的“销毁”指的是函数调用栈帧的释放但函数对象本身calculate_area这个名称所指向的代码块依然存在可以被多次调用。每次调用都是独立的互不干扰。2.2 参数传递的“秘密”既不是传值也不是传引用这是Python面试的高频题也是初学者最容易混淆的地方。Python的参数传递机制官方说法叫“按对象引用传递”。听起来有点绕我们拆开看。在Python中一切皆对象变量是对象的标签引用。当你调用函数func(a)时传递的是变量a所指向的那个对象的引用内存地址而不是对象本身的副本也不是变量a这个标签。这个机制带来的影响取决于你传递的对象类型是否可变对于不可变对象如整数、字符串、元组函数内部对参数的重新赋值只是让局部变量指向了一个新的对象不会影响外部变量。def try_change_immutable(num): num 10 # num现在指向了新的整数对象10 print(Inside function:, num) # 输出: 10 x 5 try_change_immutable(x) print(Outside function:, x) # 输出: 5 (x没变)对于可变对象如列表、字典、集合函数内部通过引用修改了对象的内容如append,update外部变量指向的同一个对象内容也就改变了。def try_change_mutable(my_list): my_list.append(4) # 修改了传入的列表对象本身 print(Inside function:, my_list) # 输出: [1, 2, 3, 4] lst [1, 2, 3] try_change_mutable(lst) print(Outside function:, lst) # 输出: [1, 2, 3, 4] (lst被改变了)理解这一点至关重要。如果你想避免函数意外修改外部的可变参数一个常见的做法是在函数内部创建副本例如使用list()构造函数、copy()方法或copy模块的deepcopy。2.3 返回值None的陷阱与多值返回的真相每个函数都有返回值。即使你没有写return语句Python也会隐式地返回None。这个None经常是bug的源头。def process_data(data): if not data: return # 这里隐式返回了None # ... 处理逻辑 return result output process_data([]) if output: # 如果data为空output是None这个判断为False可能不是你想要的行为 print(output)另一个常见技巧是“返回多个值”。实际上Python函数一次只能返回一个对象。所谓的多值返回是返回了一个元组然后利用元组的解包特性让接收方可以同时赋值给多个变量。def get_stats(numbers): total sum(numbers) count len(numbers) average total / count if count else 0 # 实际上返回的是一个元组 (total, count, average) return total, count, average # 这里发生了元组解包 sum_val, num_count, avg get_stats([1, 2, 3, 4, 5]) print(sum_val, num_count, avg) # 输出: 15 5 3.0 # 你也可以直接接收这个元组 result_tuple get_stats([1, 2, 3]) print(result_tuple) # 输出: (6, 3, 2.0)3. 参数进阶写出灵活且意图清晰的接口3.1 位置参数、关键字参数与默认参数这是定义函数接口的三种基本方式它们共同决定了函数如何被调用。位置参数最常见的参数调用时必须按定义顺序提供。关键字参数调用时通过参数名值的形式指定顺序可以打乱。这极大地提高了代码的可读性尤其是在参数较多时。def connect_to_db(host, port, username, password): pass # 关键字参数调用清晰明了 connect_to_db(hostlocalhost, port5432, usernameadmin, passwordsecret) # 顺序可以变化 connect_to_db(passwordsecret, usernameadmin, port5432, hostlocalhost)默认参数在定义时给参数一个默认值调用时可以不传递。这里有一个超级大坑默认参数的值只在函数定义时被计算并创建一次。如果默认值是可变对象如空列表[]、空字典{}所有未提供该参数的调用将共享同一个可变对象。# 错误示范一个经典的陷阱 def add_item(item, my_list[]): my_list.append(item) return my_list print(add_item(a)) # 输出: [a] print(add_item(b)) # 输出: [a, b] 第二次调用竟然看到了第一次的结果 # 正确做法使用不可变对象作为默认值在函数内部创建可变对象 def add_item_correct(item, my_listNone): if my_list is None: my_list [] # 每次调用如果需要都创建一个新的空列表 my_list.append(item) return my_list print(add_item_correct(a)) # 输出: [a] print(add_item_correct(b)) # 输出: [b] # 符合预期3.2 *args与**kwargs接收任意参数的魔法当你无法预知调用者会传递多少参数时*args和**kwargs就派上用场了。它们不是关键字只是一个约定俗成的名称你可以用*things和**options但不建议。*args用于接收任意数量的位置参数在函数内部args是一个元组。**kwargs用于接收任意数量的关键字参数在函数内部kwargs是一个字典。def logger(func_name, *args, **kwargs): print(f[LOG] Calling {func_name}) if args: print(f Positional arguments: {args}) if kwargs: print(f Keyword arguments: {kwargs}) print(- * 20) logger(process_data, 1, 2, 3, optionfast, verboseTrue) # 输出: # [LOG] Calling process_data # Positional arguments: (1, 2, 3) # Keyword arguments: {option: fast, verbose: True}在定义函数时参数顺序有严格规定(位置参数, *args, 关键字参数, **kwargs)。在调用函数时你也可以使用*和**来解包序列或字典将其内容作为参数传递。def draw_point(x, y, colorblack, size1): print(fDrawing at ({x}, {y}) with color {color}, size {size}) coordinates (5, 10) styles {color: red, size: 3} draw_point(*coordinates, **styles) # 等价于 draw_point(5, 10, colorred, size3)3.3 仅限关键字参数与仅限位置参数Python 3引入了更精细的参数控制。仅限关键字参数在*args参数之后或一个单独的*之后定义的参数调用时必须使用关键字形式。这强制提高了代码可读性常用于那些容易混淆的布尔标志参数。def calculate_payment(hours, rate, *, bonus0, tax_rate0.1): * 之后的bonus和tax_rate必须用关键字指定 base hours * rate total base bonus return total * (1 - tax_rate) # calculate_payment(40, 25, 100) # 错误bonus必须用关键字 pay calculate_payment(40, 25, bonus100) # 正确仅限位置参数在Python 3.8使用/符号可以指定其前面的参数必须通过位置传递。这通常用于保证函数签名向后兼容或者参数名本身没有明确意义时。def power(x, y, /, modNone): / 之前的x和y必须按位置传递 result x ** y if mod is not None: result % mod return result # power(x2, y3) # 错误x, y不能使用关键字 val power(2, 3) # 正确4. 函数作用域与闭包理解变量的查找规则4.1 LEGB规则变量从哪里来当你在函数内部使用一个变量名时Python按照LEGB规则依次查找L (Local)局部作用域即当前函数内部。E (Enclosing)嵌套函数的外层函数作用域非全局。G (Global)全局作用域即当前模块文件级别。B (Built-in)内置作用域Python内置的变量如len,print。x global # G def outer(): x enclosing # E def inner(): x local # L print(x) # 输出 local找到L就停止 inner() print(x) # 输出 enclosing outer() print(x) # 输出 global如果你想在函数内部修改全局变量需要使用global关键字声明。类似地在嵌套函数中修改外层函数的变量需要使用nonlocal关键字。counter 0 def increment(): global counter # 声明要修改全局变量counter counter 1 def outer(): state 0 def inner(): nonlocal state # 声明要修改外层函数变量state state 1 return state return inner inc outer() print(inc()) # 输出: 1 print(inc()) # 输出: 24.2 闭包让函数“记住”它的出生环境闭包是函数式编程中的一个核心概念。当一个嵌套函数引用了其外层函数的局部变量并且外层函数将内层函数作为返回值时就形成了一个闭包。这个内层函数“记住”了它被创建时的环境即外层函数的局部变量即使外层函数已经执行完毕。def make_multiplier(factor): 工厂函数创建一个乘以特定因子的函数 def multiplier(x): # 这里引用了外层函数的变量 factor return x * factor return multiplier # 返回内层函数而不是调用它 double make_multiplier(2) # factor2被“记住”了 triple make_multiplier(3) # factor3被“记住”了 print(double(5)) # 输出: 10 (5 * 2) print(triple(5)) # 输出: 15 (5 * 3)闭包非常强大常用于创建有状态的函数、实现装饰器、或者进行函数的部分应用柯里化。理解闭包的关键在于函数不仅是代码它还携带了它被定义时的作用域环境。5. 装饰器不修改源代码而增强函数能力装饰器可能是Python中最优雅也最“魔法”的特性之一。它本质上是一个接受函数作为参数并返回一个新函数的高阶函数。语法糖decorator让你可以非常方便地包装目标函数。5.1 自己动手写一个装饰器假设我们想给函数添加一个计时功能。import time def timer(func): 计时装饰器 def wrapper(*args, **kwargs): start_time time.perf_counter() # 高精度计时 result func(*args, **kwargs) # 执行原函数 end_time time.perf_counter() print(f函数 {func.__name__} 运行耗时: {end_time - start_time:.6f} 秒) return result return wrapper timer # 等价于 slow_function timer(slow_function) def slow_function(duration): time.sleep(duration) return Done slow_function(1) # 输出: 函数 slow_function 运行耗时: 1.001234 秒timer这行代码做了什么呢它把slow_function这个函数对象作为参数传递给了timer函数。timer函数内部定义了一个新的函数wrapper这个新函数增加了计时逻辑并在内部调用了原来的slow_function。最后timer返回这个新的wrapper函数。于是slow_function这个名字现在指向的是wrapper函数。当你调用slow_function(1)时实际上调用的是wrapper(1)。5.2 装饰器的进阶用法1. 带参数的装饰器如果你想让装饰器本身也能接受参数就需要再嵌套一层函数。def repeat(times): 执行指定次数的装饰器工厂 def decorator(func): def wrapper(*args, **kwargs): for i in range(times): result func(*args, **kwargs) return result # 通常返回最后一次的结果 return wrapper return decorator repeat(times3) def greet(name): print(fHello, {name}!) greet(World) # 输出: # Hello, World! # Hello, World! # Hello, World!这里repeat(times3)实际上先调用了repeat(3)它返回decorator函数然后再用decorator去装饰greet。2. 保留原函数元信息使用装饰器后原函数的__name__、__doc__等元信息会被wrapper函数覆盖。使用functools.wraps装饰器可以解决这个问题。from functools import wraps def logger(func): wraps(func) # 将原函数的元信息复制到wrapper函数 def wrapper(*args, **kwargs): print(fCalling {func.__name__}) return func(*args, **kwargs) return wrapper logger def add(a, b): 返回两数之和 return a b print(add.__name__) # 输出 add而不是wrapper print(add.__doc__) # 输出 返回两数之和3. 多个装饰器的执行顺序装饰器从下往上从内到外执行。decorator_a decorator_b def my_func(): pass # 等价于my_func decorator_a(decorator_b(my_func))5.3 内置装饰器staticmethod, classmethod, propertyPython在类中提供了几个非常有用的内置装饰器。staticmethod定义静态方法。它不接收隐式的self或cls参数就像一个定义在类里的普通函数主要用来组织代码表明它与类相关但不需要访问实例或类本身。class MathUtils: staticmethod def add(x, y): return x y print(MathUtils.add(1, 2)) # 通过类直接调用classmethod定义类方法。它的第一个参数是cls指向类本身不是实例。常用于创建替代构造函数。class Person: def __init__(self, name, age): self.name name self.age age classmethod def from_birth_year(cls, name, birth_year): # 通过出生年份创建Person实例 import datetime current_year datetime.date.today().year age current_year - birth_year return cls(name, age) # 等价于 Person(name, age) p Person.from_birth_year(Alice, 1990)property将方法“伪装”成属性。用于定义获取属性值的getter方法。与之配套的还有属性名.setter和属性名.deleter装饰器用于定义设置和删除属性的行为。这是实现封装和数据验证的优雅方式。class Circle: def __init__(self, radius): self._radius radius # 内部使用“保护”变量 property def radius(self): 获取半径 return self._radius radius.setter def radius(self, value): 设置半径并确保其为正数 if value 0: raise ValueError(Radius must be positive) self._radius value property def area(self): 计算面积这是一个只读属性 import math return math.pi * self._radius ** 2 c Circle(5) print(c.radius) # 像属性一样访问实际调用了getter方法 c.radius 10 # 像属性一样赋值实际调用了setter方法 print(c.area) # 只读属性 # c.area 100 # 报错因为没有定义area的setter6. 匿名函数与高阶函数函数式编程的敲门砖6.1 lambda表达式简单的单行函数lambda用于创建小巧的匿名函数。它的语法是lambda 参数列表: 表达式。表达式的结果会自动返回。它通常用于需要一个简单函数作为参数的场合比如sorted(),map(),filter()等函数的key参数。# 普通函数定义 def add(x, y): return x y # 等价的lambda表达式 add_lambda lambda x, y: x y print(add(1, 2)) # 输出: 3 print(add_lambda(1, 2)) # 输出: 3 # 更常见的用法作为参数传递 students [(Alice, 88), (Bob, 72), (Charlie, 95)] # 按分数排序key函数接收一个元组返回其第二个元素分数 students_sorted sorted(students, keylambda student: student[1], reverseTrue) print(students_sorted) # 输出: [(Charlie, 95), (Alice, 88), (Bob, 72)]注意lambda函数只能包含一个表达式不能包含复杂的语句如if-elif-else的多分支、循环、return等。如果逻辑复杂请使用def定义常规函数。过度使用或滥用lambda会降低代码可读性。6.2 map, filter, reduce经典的高阶函数这些函数接受一个函数和一个可迭代对象作为参数对可迭代对象中的元素进行某种处理。map(func, iterable)将func应用于iterable的每个元素返回一个map对象可迭代。numbers [1, 2, 3, 4, 5] squared map(lambda x: x ** 2, numbers) print(list(squared)) # 输出: [1, 4, 9, 16, 25] # 通常用列表推导式更直观: [x**2 for x in numbers]filter(func, iterable)用func测试iterable中的每个元素返回使func返回True的元素组成的filter对象。numbers [1, 2, 3, 4, 5, 6] evens filter(lambda x: x % 2 0, numbers) print(list(evens)) # 输出: [2, 4, 6] # 列表推导式等价: [x for x in numbers if x % 2 0]functools.reduce(func, iterable[, initial])用func对iterable中的元素进行累积计算。func接受两个参数第一个是累积值第二个是当前元素。from functools import reduce numbers [1, 2, 3, 4, 5] product reduce(lambda acc, x: acc * x, numbers) # 计算连乘 print(product) # 输出: 120 (1*2*3*4*5) sum_with_init reduce(lambda acc, x: acc x, numbers, 10) # 从10开始累加 print(sum_with_init) # 输出: 25 (1012345)在现代Python中列表推导式和生成器表达式在很多场景下比map和filter更受青睐因为它们通常更易读。但reduce在需要自定义累积逻辑时仍有其价值。7. 生成器函数与yield惰性求值与无限序列当你看到函数里出现了yield关键字它就变成了一个生成器函数。调用生成器函数不会立即执行其代码而是返回一个生成器对象。这个对象是一个迭代器每次调用next()或用于for循环时它才会执行函数体直到遇到yield返回yield后面的值并暂停在此处保存所有局部状态。下次再被请求时从上次暂停的地方继续执行。7.1 创建简单的生成器def count_up_to(max_num): 生成从1到max_num的数字 count 1 while count max_num: yield count # 每次执行到这里就暂停并返回count count 1 # 创建生成器对象 counter count_up_to(3) print(next(counter)) # 输出: 1 print(next(counter)) # 输出: 2 print(next(counter)) # 输出: 3 # print(next(counter)) # 再调用会抛出 StopIteration 异常 # 更常见的用法直接用for循环迭代 for num in count_up_to(5): print(num, end ) # 输出: 1 2 3 4 5生成器的核心优势是惰性求值和节省内存。它不需要像列表一样一次性生成所有数据并存储在内存中而是按需生成。这对于处理大规模数据流如大文件读取、无限序列、实时数据流至关重要。7.2 生成器的双向通信send()与close()生成器不仅可以通过yield返回值还可以通过send(value)方法接收外部传入的值并通过yield表达式接收。def running_average(): 计算运行平均值 total 0 count 0 average None while True: value yield average # yield作为表达式可以接收send进来的值 if value is None: break # 如果send(None)或next()value为None我们选择退出 total value count 1 average total / count avg_gen running_average() next(avg_gen) # 启动生成器执行到第一个yield处此时average为None返回None print(avg_gen.send(10)) # 发送10value10计算平均值为10.0返回 10.0 print(avg_gen.send(20)) # 发送20value20计算平均值为15.0返回 15.0 print(avg_gen.send(30)) # 发送30value30计算平均值为20.0返回 20.0 avg_gen.close() # 关闭生成器此外生成器还有throw(exception)方法用于向生成器内部抛出异常。这使得生成器可以实现非常复杂的协程逻辑也是asyncio异步编程的基础。7.3 生成器表达式更简洁的惰性容器生成器表达式是创建匿名生成器的语法糖形式类似于列表推导式但使用圆括号()。# 列表推导式立即计算返回列表 squares_list [x**2 for x in range(1000000)] # 占用大量内存 # 生成器表达式惰性计算返回生成器对象 squares_gen (x**2 for x in range(1000000)) # 几乎不占内存 print(next(squares_gen)) # 输出: 0 print(next(squares_gen)) # 输出: 1 # ... 只有在需要时才计算下一个值在处理大数据集时养成使用生成器表达式的习惯可以显著降低程序的内存峰值。8. 函数式编程工具functools与itertoolsPython的functools和itertools模块提供了大量用于函数式编程和高效迭代的工具。8.1 functools.partial函数柯里化与参数冻结partial用于“冻结”函数的部分参数生成一个新的、参数更少的函数。这在回调函数或需要固定某些参数的场景下非常有用。from functools import partial def power(base, exponent): return base ** exponent # 创建一个总是计算平方的新函数 square partial(power, exponent2) # 创建一个总是以2为底数的指数函数 exp_base2 partial(power, base2) print(square(5)) # 输出: 25 (5**2) print(exp_base2(3)) # 输出: 8 (2**3) print(exp_base2(10)) # 输出: 1024 (2**10)8.2 functools.lru_cache自动缓存提升递归性能这是一个极其强大的装饰器用于为函数添加最近最少使用LRU缓存。对于计算昂贵且经常被相同参数调用的函数如纯函数、递归函数它能带来指数级的性能提升。from functools import lru_cache import time lru_cache(maxsize128) # 最多缓存128个不同的调用结果 def fibonacci(n): if n 2: return n return fibonacci(n-1) fibonacci(n-2) start time.perf_counter() result fibonacci(35) end time.perf_counter() print(ffibonacci(35) {result}, 耗时: {end-start:.4f}秒) # 不加缓存可能需要数秒加上缓存后几乎是瞬间完成。lru_cache缓存的是参数和返回值的映射。参数必须是可哈希的通常意味着不可变。maxsize指定缓存大小设为None表示无限缓存可能消耗大量内存。8.3 itertools高效迭代的瑞士军刀itertools模块提供了众多用于操作迭代器的函数。这里仅举几例itertools.cycle(iterable)无限循环一个可迭代对象。itertools.chain(*iterables)将多个可迭代对象连接成一个。itertools.product(*iterables, repeat1)计算笛卡尔积常用于多层循环。itertools.permutations(iterable, rNone)生成排列。itertools.combinations(iterable, r)生成组合。import itertools # 模拟一个简单的状态机循环 states [IDLE, RUNNING, ERROR] state_cycle itertools.cycle(states) for _ in range(7): print(next(state_cycle), end ) # 输出: IDLE RUNNING ERROR IDLE RUNNING ERROR IDLE # 扁平化多个列表 list1 [1, 2] list2 [a, b] for item in itertools.chain(list1, list2): print(item, end ) # 输出: 1 2 a b9. 类型提示与注解让函数接口更清晰从Python 3.5开始引入了类型提示Type Hints。它不会影响程序的运行时行为Python仍然是动态类型语言但可以让你的代码意图更清晰并配合IDE如PyCharm, VSCode和静态类型检查工具如mypy来提前发现潜在的类型错误。from typing import List, Tuple, Optional, Union def process_items( items: List[str], # items是一个字符串列表 threshold: int 0, # threshold是整数默认0 verbose: bool False # verbose是布尔值 ) - Tuple[int, Optional[str]]: # 返回一个元组包含一个整数和一个可选的字符串 处理字符串列表返回处理数量和可能的消息 count 0 message None for item in items: if len(item) threshold: count 1 if verbose: print(fProcessing: {item}) if count 0: message No items processed return count, message # 使用示例 result_count, result_msg process_items([apple, banana, cat], threshold3, verboseTrue) print(result_count, result_msg)类型提示就像给函数参数和返回值加上了注释极大地提升了代码的可读性和可维护性尤其是在团队协作和大型项目中。虽然学习typing模块需要一点成本但长远来看非常值得。10. 实战避坑与性能调优经验10.1 避免在循环中定义函数这是一个常见的性能陷阱和逻辑错误。# 低效且可能出错的写法 results [] for i in range(5): def func(x): return x i # 注意这里的i是闭包引用的是循环结束后的最终值 results.append(func) print([f(10) for f in results]) # 你期望输出 [10, 11, 12, 13, 14] 吗 # 实际输出: [14, 14, 14, 14, 14] 所有函数都记住了i4 # 正确写法1使用默认参数绑定当前值 results [] for i in range(5): def func(x, ii): # 使用默认参数将i的当前值“冻结”在函数定义时 return x i results.append(func) print([f(10) for f in results]) # 输出: [10, 11, 12, 13, 14] # 正确写法2使用工厂函数 def make_adder(n): def adder(x): return x n return adder results [make_adder(i) for i in range(5)] print([f(10) for f in results]) # 输出: [10, 11, 12, 13, 14]10.2 谨慎使用可变对象作为默认参数前面已经提到这是Python新手最常踩的坑之一。牢记默认参数的值在函数定义时计算一次然后重复使用。对于列表、字典、集合等可变对象务必使用None作为默认值在函数内部进行初始化。10.3 理解函数调用的开销函数调用在Python中是有开销的创建栈帧、参数传递等。在性能关键的最内层循环中过度细化的函数调用可能会成为瓶颈。这时可以考虑内联代码将简单函数体直接写在循环里或者使用functools.lru_cache缓存结果。当然在绝大多数情况下代码的清晰度比这点微优化重要得多不要过早优化。10.4 使用__doc__和类型提示写好文档一个良好的函数应该是不看函数体就能知道怎么用的。__doc__字符串文档字符串和类型提示是达成这一目标的最佳工具。def calculate_compound_interest( principal: float, annual_rate: float, years: int, compounding_per_year: int 1 ) - float: 计算复利。 参数: principal: 本金。 annual_rate: 年利率例如0.05表示5%。 years: 投资年数。 compounding_per_year: 每年复利次数默认为1年复利。 返回: 投资到期后的总金额。 rate_per_period annual_rate / compounding_per_year periods years * compounding_per_year amount principal * (1 rate_per_period) ** periods return round(amount, 2)这样的函数任何人调用时通过IDE的提示或help(calculate_compound_interest)就能立刻明白其用途和用法。函数是Python的灵魂。从简单的代码复用工具到构建复杂系统的抽象单元再到函数式编程和元编程的基石它的内涵远比def和return这两个关键字丰富。我个人的体会是评判一个Python程序员水平如何看他写的函数就能知道个大概。是充满了全局变量和副作用的“意大利面条代码”还是接口清晰、职责单一、测试友好的模块化单元这其中的差距往往就体现在对这些函数细节的理解和运用上。下次当你再写一个函数时不妨多花几分钟想想参数这样设计清晰吗有没有意外的副作用错误处理够吗类型提示加了吗文档写清楚了吗这些小习惯最终会决定你代码的质量和你的编程效率。