目录
函数介绍
函数是一段可以重复使用的代码,只需要定义一次,之后就可以用不同的参数进行调用。
- 有内置函数,如求绝对值、最大值、类型转换等,也可以自定义函数
- 调用函数时要传入正确数量和类型的参数,否则会报错
- 函数名其实就是一个变量,可以赋值给其他变量
# 求绝对值
print(abs(-10)) # 输出:10
# 求最大值
print(max(3, 7, 2, 5)) # 输出:7
# 类型转换
print(int("123")) # 字符串转整数,输出:123
print(float("3.14")) # 字符串转浮点数,输出:3.14
print(str(456)) # 数字转字符串,输出:'456'
print(bool(0)) # 0转为布尔值,输出:False
print(bool("hi")) # 非空字符串转为布尔值,输出:True
# abs函数只能传一个参数
# print(abs(1, 2)) # TypeError: abs() takes exactly one argument (2 given)
# abs函数不能传字符串
# print(abs("abc")) # TypeError: bad operand type for abs(): 'str'
# 给abs函数起个新名字
my_abs = abs
print(my_abs(-20)) # 输出:20
# 自定义函数
def area(r: float) -> float:
"""计算圆面积。r 为半径(米)。"""
from math import pi
return pi * r * r # 用return返回结果
print(area.__doc__) # -> 计算圆面积。r 为半径(米)。
help(area) # 会显示这段说明
# 调用自定义函数
print(area(5)) # 输出:78.539815
print(area(10)) # 输出:314.15926
# 把整数转换为十六进制字符串
n1 = 255
n2 = 1000
print(hex(n1)) # 输出:0xff
print(hex(n2)) # 输出:0x3e8定义函数
- 用
def关键字,后面跟上函数名、括号、参数和冒号 - 函数体要缩进
- 返回值可以用
return返回,没写return,默认返回None - 函数可以有多个返回值
- 空函数可以用
pass占位,pass语句是一个空操作 - 参数类型,可以用
isinstance()手动检查
# 定义一个计算绝对值的函数
def my_abs(num):
# 如果num大于等于0,直接返回num
if num >= 0:
return num
# 否则返回-num
else:
return -num
# 调用函数,传入-8
print(my_abs(-8)) # 输出:8
# 多个返回值
def split_pair(s):
return s[:len(s)//2], s[len(s)//2:]
left, right = split_pair("abcd") # 左右解包
# 定义一个什么都不做的函数
def do_nothing():
pass # pass表示占位,什么也不做 这样代码不会报错
# 定义一个只接受数字参数的绝对值函数
def safe_abs(x):
# 检查x是不是整数或浮点数
if not isinstance(x, (int, float)):
raise TypeError("参数类型必须是数字")
if x >= 0:
return x
else:
return -x
safe_abs("abc") # 这一行会报TypeError: 参数类型必须是数字参数
- 必须按照正确的顺序传入参数,
=为参数设置默认值 *args可变位置参数,把额外的所有“位置实参”打包成一个元组**kwargs把额外的所有“关键字实参”收集成一个字典- 命名参数,把参数写在
*之后(或在有*args时,写在它后面) /左边的参数仅限位置参数(Python 3.8+,PEP 570)
# 位置参数(positional-only)
def calculate_area(length, width):
"""
计算矩形面积
length: 长度
width: 宽度
"""
return length * width # 返回长度乘以宽度的结果
# 调用函数
print(calculate_area(5, 3)) # 输出:15
# / 分隔符
def f(a, b, /, c, d):
print(a, b, c, d)
f(1, 2, 3, 4) # ✅ 都用位置传
f(1, 2, c=3, d=4) # ✅ c、d 可关键字传
f(a=1, b=2, c=3, d=4) # ❌ TypeError:a、b 是仅限位置
# 命名关键字参数(keyword-only parameters)
# 调用时必须用“参数名=值”的形式传入,不能当位置参数传。
def f(x, *, a, b=0):
print(x, a, b)
f(10, a=1, b=2) # ✅
f(10, 1, 2) # ❌ TypeError:a/b 必须用关键字传
# 可以和 `*args` 一起用
def g(*args, a, b=0):
print(args, a, b)
g(1, 2, a=3) # args=(1,2), a=3, b=0
# 可变位置参数(var-positional) → `*args`
def sum_all(*nums):
return sum(nums)
sum_all(1, 2, 3) # 6
sum_all() # 0(空元组)
# 位置不确定 → 用 `*args`
# 关键字不确定 → 用 `**kwargs`
# 既有固定参数、又想允许“更多” → 组合使用
# 还想限制/命名某些关键字 → 配合命名关键字参数
# 综合使用示例
def api_call(endpoint, /, *path_params, method="GET", timeout=5, **query):
"""
endpoint 位置仅限参数(/ 前)
*path_params 不定数量的位置片段
method/timeout 命名关键字参数(必须用名字传)
**query 任意数量的关键字参数(作为查询串)
"""
print(endpoint, path_params, method, timeout, query)
api_call("/v1/user", 25, method="POST", timeout=3, sort="desc", limit=10)
# 输出:
# /v1/user (25,) POST 3 {'sort': 'desc', 'limit': 10}注意点
- “命名关键字参数”和
**kwargs- 命名关键字参数是白名单:只接受你明确列出的那些名字;
**kwargs是兜底:把其它所有关键字都收进来。
*args后面的参数无法用位置传:*args后面出现的非星号参数默认就是命名关键字参数,必须写name=值。
递归函数
递归函数就是“函数自己调用自己”,必须包含两个关键部分:
- 停止条件(base case)
- 递推关系(recursive case)要确保收敛
# 阶乘
def fact(n):
# 终止条件 / 边界情形
if n <= 1:
return 1
# 拆解规则 / 递归展开(规模收敛:n -> n-1)
return n * fact(n - 1)
fact(5)
# 二分查找
def binary_search(arr, target, left, right):
# 基本情况:如果左边界大于右边界,说明没找到
if left > right:
return -1
# 计算中间位置
# `//` 是整除(向下取整的除法,floor division)运算符
mid = (left + right) // 2
# 如果找到目标值,返回位置
if arr[mid] == target:
return mid
# 如果中间值大于目标值,在左半部分查找
if arr[mid] > target:
return binary_search(arr, target, left, mid - 1)
# 如果中间值小于目标值,在右半部分查找
return binary_search(arr, target, mid + 1, right)
# 测试函数
numbers = [1, 3, 5, 7, 9, 11, 13, 15]
target = 7
result = binary_search(numbers, target, 0, len(numbers) - 1)
print(f"数字{target}在列表中的位置是:{result}")生成器
一种可迭代、惰性产出数据的对象。它一次只产出一个值,不把所有结果一次性放进内存,特别适合大数据/流式处理。
生成器对象实现了迭代器协议,这就是能被 for 遍历的原因。
优点有:
- 省内存:不把全部结果存到列表里。
- 可组合:像搭积木那样把多个生成器串联成管道。
- 可中断:消费者停止迭代,生产者也就停了(天然“背压”)。
生成器表达式的语法与列表生成式非常相似
- 生成器使用圆括号
() - 列表使用方括号
[] ()和列表推导[x*x for ...]的区别:不立刻计算,更省内存。
生成器函数(yield)
yield会把函数变成生成器函数;调用它不会执行函数体,而是返回一个生成器对象。- 每次
next()或for迭代都会从上次yield处继续执行,直到遇到下一个yield或函数结束(抛StopIteration)。 yield from(把迭代“委托”出去)
def countdown(n):
while n > 0:
yield n # 产出一个值并“暂停”
n -= 1 # 下次迭代从这里继续
for x in countdown(3): # 3, 2, 1
print(x)
# 把迭代“委托”出去
def flatten(nested):
for row in nested:
yield from row # 等价于:for x in row: yield x
# 把递归改为生成器
def inorder(root):
if root:
yield from inorder(root.left)
yield root.val
yield from inorder(root.right)生成器表达式
gen = (x*x for x in range(5)) # 惰性
list(gen) # [0, 1, 4, 9, 16]
# 典型用法:和 `sum`、`any`、`all` 等“消费一次就完”的函数搭配:
total = sum(x*x for x in range(10_000_000))
# 管道示例
def read_lines(fp):
for line in fp:
yield line.rstrip("\n")
def filter_nonempty(lines):
for s in lines:
if s.strip():
yield s
def to_ints(lines):
for s in lines:
yield int(s)
with open("nums.txt") as f:
for n in to_ints(filter_nonempty(read_lines(f))):
print(n)双向通信 & 控制
生成器不仅能“产出”(yield),还可以接收值或异常(协程原型):
g.send(value):把value送回到上次yield表达式,并继续运行到下一个yield。g.throw(exc):向生成器抛异常(在yield处),可被生产者捕获处理。g.close():触发GeneratorExit让生成器体面收尾。
日常开发里更常见的是“只读”的生成器;
send/throw/close多用于高级协程或框架内部。
# 双向通信 & 控制
def accumulator():
total = 0
while True:
x = yield total # 接收 send() 过来的值
if x is None: # None 表示“只想读总计”
continue
total += x
gen = accumulator()
next(gen) # 预激活 → 得到初始 total=0
gen.send(10) # -> 10
gen.send(5) # -> 15
gen.close() # 结束迭代器
生成器就是“迭代器工厂”,带 yield 的生成器函数返回的对象天然是迭代器。
- 迭代器维护迭代状态、一次产出一个元素的对象;实现了
__iter__()(返回自身) 和__next__()(取下一个;无则抛StopIteration)。 - 可迭代对象不一定是迭代器,但可以通过
iter()函数转换为迭代器。迭代器总是可迭代的。
# 可以使用 isinstance () 函数来检查一个对象是否为可迭代对象或迭代器。
# Iterable(可迭代对象):表示可以被迭代的对象。
# Iterator(迭代器):表示一个数据流,可以通过 next () 函数逐个获取元素。
from collections.abc import Iterable, Iterator
# 创建一个列表(可迭代对象)
fruits = ['苹果', '香蕉', '橙子']
# 检查是否为可迭代对象
print(isinstance(fruits, Iterable)) # 输出:True
# 迭代对象不是迭代器
print(isinstance(fruits, Iterator)) # 输出:False
# 将可迭代对象转换为迭代器
fruit_iterator = iter(fruits)
# 检查是否为迭代器
print(isinstance(fruit_iterator, Iterable)) # 输出:True
# 迭代器是可迭代对象
print(isinstance(fruit_iterator, Iterator)) # 输出:True
# for x in obj 的幕后
it = iter(obj) # 调 obj.__iter__() 得到迭代器
while True:
try:
x = next(it) # 调 it.__next__()
except StopIteration:
break
# 使用 x
# 把可迭代对象变成迭代器
it = iter([10, 20, 30])
next(it) # 10
next(it) # 20
next(it) # 30
next(it) # StopIteration
# 自定义迭代器
class CountDown:
def __init__(self, n): self.n = n
def __iter__(self): return self # 迭代器返回自身
def __next__(self):
if self.n <= 0: raise StopIteration # 告诉外界“没有更多元素了”
self.n -= 1
return self.n + 1
for x in CountDown(3): # 3, 2, 1
print(x)
# 迭代器工厂
def countdown(n):
while n > 0:
yield n
n -= 1
# `raise` 关键词,抛出异常用的语句。抛出后,当前流程会立刻中断,转去寻找匹配的 `except` 处理;若没人接住,程序就报错结束。
raise ValueError("bad input") # 抛出内置异常
raise MyError("oops") # 抛出自定义异常
raise RuntimeError from exc # 异常链(把原因exc附带上)
# `StopIteration` 异常类,表示迭代已经结束,这是迭代器协议里规定的“终止信号”。
# `for` 循环、`list(iterable)` 等迭代消费者看见这个异常就会正常停止,不会把它当错误显示出来。
# 在生成器函数(用 `yield` 的函数)内部,不要手动 `raise StopIteration` 来结束
# 正确做法是自然走到函数末尾或 `return 值`(`return` 的值会成为 `StopIteration.value`)
# Python 3.7 起 `StopIteration` 从生成器冒出,会被转成 `RuntimeError`(PEP 479)
def gen_ok():
yield 1
return 42 # 推荐:结束并带“返回值”
def gen_bad():
yield 1
raise StopIteration(42) # 会被改写成 RuntimeError("generator raised StopIteration")生成器函数 vs 生成器 vs 迭代器
关系链:生成器对象 ⊂ 迭代器 ⊂ 可迭代对象
- 可迭代对象(Iterable):能用于
for … in/iter()的东西。
例:list、tuple、str、dict、set、range、以及所有能返回迭代器的对象。 - 迭代器(Iterator):带有状态、实现了
__iter__()(返回自身)和__next__()(取下一个,没了抛StopIteration)的对象。
例:iter(list)得到的<list_iterator>、文件对象、itertools产物等。 - 生成器对象(Generator):由含
yield的函数或生成器表达式创建的“特殊迭代器”;除了是迭代器,还支持send()/throw()/close()等扩展接口。
# 定义一个生成器函数
def my_generator():
yield 1
yield 2
yield 3
# 创建生成器对象
gen = my_generator()
# 使用next()获取数据
print(next(gen)) # 输出:1
print(next(gen)) # 输出:2
print(next(gen)) # 输出:3
# 使用for循环遍历生成器
for num in my_generator():
print(num) # 输出:1, 2, 3
lambda
lambda 表达式就是用一行写出的匿名函数
- 创建并返回一个函数对象(名字显示为
<lambda>)。 - “表达式”的值就是返回值(没有
return关键字)。 - 可以有默认参数
- 只能写单个表达式,不能写语句(如
for/while/if:块、try等)。
lambda 参数列表: 表达式
# 用lambda表达式实现两个数相加
add = lambda x, y: x + y # 定义一个匿名函数,接收x和y,返回它们的和
print(add(3, 5)) # 输出: 8
# 作为回调 / key 函数
nums = [3, -10, 5]
sorted(nums, key=lambda x: abs(x)) # [3, 5, -10]
# 等价写法
def keyfunc(x):
return abs(x)
sorted(nums, key=keyfunc)
# map / filter 的轻量函数
list(map(lambda x: x*x, [1, 2, 3])) # [1, 4, 9]
list(filter(lambda s: s.isalpha(), ["a", "3", "bc"])) # ['a', 'bc']
# 条件表达式
f = lambda x: "even" if x % 2 == 0 else "odd"
f(3) # 'odd'
# 只能是表达式,不能包含语句;但可以用条件表达式、逻辑运算、以及海象运算符 `:=` 等表达式技巧:
lam = lambda s: (n := len(s)) * (n > 3) # 长度>3则返回长度,否则返回0
list(map(lam, ["a", "bb", "ccc", "dddd"]))
# 默认参数
num = 5 # 定义一个变量num,赋值为5
func = lambda i=num: i * i # 参数i默认值为5,函数返回i的平方
print(func()) # 由于没有传参,使用默认值5,结果为25函数式编程
- 纯函数:同样输入→同样输出;不读/改全局状态,不做 I/O 等副作用
- 不可变性:数据一旦创建就不修改(创建新值代替原地修改)
- 高阶函数:函数是“一等公民”,可作为参数/返回值
- 函数组合:把小函数像积木一样组合成大功能
- 递归/映射:用
map/filter/reduce、递归等替代显式可变循环 - 引用透明:表达式可用其结果替换而不改变程序行为
# 纯函数
# 纯函数没有副作用,副作用是指函数除了返回值之外,还对外部环境产生了影响。
def square(x): return x * x
# map / filter / reduce(functools.reduce 需导入)
from functools import reduce
nums = [1, 2, 3, 4]
squares = list(map(square, nums)) # [1, 4, 9, 16]
evens = list(filter(lambda x: x % 2 == 0, nums)) # [2, 4]
total = reduce(lambda a, b: a + b, nums) # 10
# 列表/字典推导(更 Pythonic——符合 Python 风格的 的 map/filter)
squares2 = [x*x for x in nums]
evens2 = [x for x in nums if x % 2 == 0]
# 偏函数 / 组合
from functools import partial
import operator
add10 = partial(operator.add, 10) # add10(x) == 10 + x
# 不可变类型
point = (3, 4) # tuple 不可变
tags = frozenset({"a", "b"})
# 高阶函数
def process_numbers(numbers, filter_func, map_func):
"""
处理数字列表
:param numbers: 数字列表
:param filter_func: 过滤函数
:param map_func: 映射函数
:return: 处理后的列表
"""
# 先过滤,再映射
filtered = [n for n in numbers if filter_func(n)] # 使用过滤函数
result = [map_func(n) for n in filtered] # 使用映射函数
return result
# 定义过滤和映射函数
def is_even(n): # 判断是否为偶数
return n % 2 == 0
def double(n): # 将数字翻倍
return n * 2
# 使用高阶函数处理数据
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
result = process_numbers(numbers, is_even, double)
print(f"处理结果: {result}") # 输出: [4, 8, 12, 16, 20]
# 应用示例
# 这段代码用到了函数式编程的元素(一等函数 + 高阶函数 + 闭包),但它不是“纯粹的函数式编程”。原因是里面有可变状态(`count += 1`),属于副作用,因此 `counter()` 不是纯函数(相同输入并不保证相同输出)。
def create_counter():
"""
创建一个计数器函数
:return: 计数器函数
"""
count = 0 # 计数器初始值
def counter(): # 内部函数
# nonlocal 关键字用于声明使用外部变量
nonlocal count # 声明使用外部变量
count += 1 # 计数器加1
return count # 返回当前计数
return counter # 返回计数器函数
# 测试代码
counter1 = create_counter() # 创建第一个计数器
counter2 = create_counter() # 创建第二个计数器
print("第一个计数器:")
print(counter1()) # 输出: 1
print(counter1()) # 输出: 2
print(counter1()) # 输出: 3
print("\n第二个计数器:")
print(counter2()) # 输出: 1
print(counter2()) # 输出: 2
# 闭包 返回函数
def count(n):
fs = []
for i in range(1, 4):
fs.append(lambda i = i: i * i)
return fs
fs = count(3)
print(fs)
print(fs[0]())
print(fs[1]())
print(fs[2]())map(function, iterable, …)
把 function 依次作用到可迭代里的每个元素,返回一个惰性迭代器
- 直接传现成函数(如 str.strip, int)可读性很高:map(int, parts)。
- 流式处理、需要惰性时:把 map 串起来很自然。
# 基本用法
list(map(abs, [-1, 2, -3])) # [1, 2, 3]
list(map(str.upper, ["a", "b"])) # ['A', 'B']
# 多个可迭代会“并行”取值,长度以最短的为准
a, b = [1, 2, 3], [10, 20]
list(map(lambda x, y: x + y, a, b)) # [11, 22]
# 等价写法
[x*x for x in range(5)] # 列表推导式
(x*x for x in range(5)) # 生成器表达式(同样惰性)
[x+y for x, y in zip(a, b)] # 多输入时配 zipreduce(function, iterable, initializer)
reduce 函数需要从 functools 模块导入
from functools import reduce
from operator import add, mul
# 求和 / 求积(其实直接用内置更好)
reduce(add, [1, 2, 3, 4], 0) # 10
reduce(mul, [1, 2, 3, 4], 1) # 24
# 把字符串拼起来
reduce(add, ["py", "thon"], "") # 'python'
# 更推荐 ''.join(...)
"".join(["py", "thon"])
# 高级用法
# 1) 函数组合(把多个函数合成一个)
from functools import reduce
def compose(*funcs):
return lambda x: reduce(lambda v, f: f(v), reversed(funcs), x)
f = compose(str, abs) # 等价于 lambda x: str(abs(x))
f(-3) # '3'
# 2) 自定义聚合:累加到字典/集合(也可用循环/Counter)
def merge_counts(acc, x):
acc[x] = acc.get(x, 0) + 1
return acc
reduce(merge_counts, "banana", {}) # {'b':1,'a':3,'n':2}
# 3) 扁平化
lists = [[1,2],[3,4],[5]]
# 不推荐:
reduce(add, lists, []) # list 拼接会 O(n^2)
# 更推荐 itertools.chain
from itertools import chain
list(chain.from_iterable(lists)) # 更高效filter(func, iterable)
把 iterable 里的元素按条件函数 func 的真假过滤,返回一个惰性迭代器(Python 3)。
- func(x) 为真 → 保留 x
- func(x) 为假 → 丢弃 x
- func 为 None 时,等价于 func = bool:去掉所有“假值”(0、”、[]、{}、None、False 等)
特性与注意点
- 惰性:filter 返回 filter 对象,要用 for 或 list() 消费
- 只接收一个可迭代(不像 map 可多个)
- 谨慎副作用:func 最好是无副作用的纯判断
- 顺序保持:输出顺序与输入一致
# 只保留偶数
list(filter(lambda x: x % 2 == 0, range(10))) # [0, 2, 4, 6, 8]
# 去掉空白行
lines = ["a", " ", "", "b"]
list(filter(str.strip, lines)) # ['a', 'b']
# 过滤掉“假值”
xs = [0, 1, "", "hi", None, [], [1]]
list(filter(None, xs)) # [1, 'hi', [1]]
# 等价于推导式
[x for x in range(10) if x % 2 == 0]
(s for s in lines if s.strip()) # 惰性:生成器表达式
# 例子:筛选出所有能被3整除的数
nums = list(range(1, 21))
# 用lambda表达式直接写判断逻辑
divisible_by_3 = filter(lambda x: x % 3 == 0, nums)
print(list(divisible_by_3)) # 输出: [3, 6, 9, 12, 15, 18]sorted
- 接受任何可迭代对象(list/tuple/set/str/生成器…),返回新的列表(不会原地改动原数据)
- Python 使用 Timsort,时间复杂度约 O(n log n),稳定排序(相等键保持原相对顺序)
list.sort 的区别
- sorted(…):返回新列表;能排序任意 iterable
- list.sort(…):就地排序该列表,返回 None(更省内存/稍快)
# 语法
sorted(iterable, /, *, key=None, reverse=False) -> list
# list.sort 就地改动,返回 None
list.sort(self, /, *, key=None, reverse=False)
names = ["Tom", "alice", "BOB"]
sorted(names) # ['BOB','Tom','alice'](默认大小写先大写)
sorted(names, key=str.lower) # ['alice','BOB','Tom'](忽略大小写)
sorted(names, key=str.casefold) # 更稳的大小写折叠
# 多字段排序
from operator import attrgetter
records = [
{"name":"A", "age":30, "score":90},
{"name":"B", "age":30, "score":85},
{"name":"C", "age":25, "score":95},
]
# 先按 age 升序,再按 score 降序
sorted(records, key=lambda r: (r["age"], -r["score"]))
# 对对象:
class Emp:
def __init__(self, name, dept, age):
self.name = name
self.dept = dept
self.age = age
objs = [
Emp("Alice", "Sales", 30),
Emp("Bob", "Sales", 25),
Emp("Caro", "Tech", 28),
]
# 先按 dept 升序,再按 age 升序(稳定排序)
out = sorted(objs, key=attrgetter("dept", "age"))
[(o.name, o.dept, o.age) for o in out]
# → [('Alice','Sales',30), ('Bob','Sales',25), ('Caro','Tech',28)] # 注意稳定性
# 字典相关的排序
d = {"x": 5, "y": 2, "z": 9}
sorted(d) # ['x','y','z'] —— 按键
sorted(d.items(), key=lambda kv: kv[1], reverse=True) # 按值
# 若想得到“按值排序后”的字典(Py3.7+字典保持插入顺序):
dict(sorted(d.items(), key=lambda kv: kv[1]))
# 处理 None/缺失值
rows = [{"age": 30}, {"age": None}, {"age": 20}]
# 让 None 排在最后:
sorted(rows, key=lambda r: (r["age"] is None, r["age"]))
# 小抄
sorted(xs) # 基本
sorted(xs, reverse=True) # 降序
sorted(xs, key=len) # 按长度
sorted(xs, key=lambda s: (len(s), s)) # 多键 先按长度,再按字母序
sorted(d.items(), key=lambda kv: kv[1]) # 字典按值partial & partialmethod
partial 预先固定函数的部分位置/关键字参数,返回一个新函数,后续只需提供剩余参数。
partialmethod 和 partial 的区别
- partial 返回的是可调用对象,但不是方法描述符;放到类属性上不会自动绑定 self,常导致 “missing 1 required positional argument: ‘self’”。
- partialmethod 返回的是方法描述符;放到类里后,通过实例访问会正确绑定 self,像正常方法一样用。
# 语法 partial(func, /, *args, **kwargs)
# 返回 newfunc,调用时执行 func(*args, *new_args, **kwargs, **new_kwargs)。
from functools import partial
def power(base, exp):
return base ** exp
square = partial(power, exp=2) # 先把 exp 固定为 2
square(9) # 81 —— 等价 power(9, exp=2)
# partialmethod
# partialmethod(func, /, *args, **kwargs) 是一个描述符(descriptor),专门用于在类里定义“预先固定了一部分参数”的方法。
# 调用效果等价:func(self, *pre_args, *call_args, **pre_kwargs, **call_kwargs)
from functools import partialmethod
class Greeter:
def speak(self, who, punct="!"):
return f"Hello, {who}{punct}"
hi = partialmethod(speak, punct=" :)") # 预绑定 punct
# 对比差异
class A:
def add(self, x, y):
return x + y
add5_wrong = partial(add, 5) # ❌ 不会绑定 self
add5 = partialmethod(add, 5) # ✅ 正确:先把 self 绑定,再把 5 拼上
a = A()
a.add5_wrong(10) # TypeError: missing 'self'
a.add5(10) # 15装饰器
装饰器(decorator)是一种在不改动函数/方法/类源码与调用方式的前提下,给它“加功能”的写法。技术上,装饰器就是接收一个可调用对象并返回一个可调用对象的高阶函数(或类)。
- 多个装饰器的执行顺序,从下往上应用、从上到下包裹
- 装饰 async def 时,wrapper 也要 async,并 await fn(…)
# 定义一个简单的装饰器,在函数执行前打印日志
def log(func):
def wrapper(*args, **kw):
print('开始执行函数:', func.__name__) # 打印函数名
result = func(*args, **kw) # 调用原函数,* 和 ** 在这里起到解包作用
print('函数执行完毕') # 打印结束信息
return result # 返回原函数的结果
return wrapper # 返回包装后的函数
# 使用装饰器
@log
def hello(name, age,**kw):
print(name, age, kw)
hello('张三', 20,city='上海', job='工程师')
# 开始执行函数: hello
# 张三 20 {'city': '上海', 'job': '工程师'}
# 函数执行完毕
# 函数执行计时
# 定义一个装饰器,计算函数执行时间
import time
def timer(func):
def wrapper(*args, **kw):
start = time.time() # 记录开始时间
result = func(*args, **kw) # 调用原函数
end = time.time() # 记录结束时间
print(f'函数 {func.__name__} 执行了 {end - start:.2f} 秒') # 打印执行时间
return result # 返回原函数的结果
return wrapper # 返回包装后的函数
# 使用装饰器
@timer
def slow_function():
time.sleep(1) # 模拟耗时操作
print('函数执行完毕')
slow_function()
#函数执行完毕
#函数 slow_function 执行了 1.02 秒
import asyncio, time
from functools import wraps
# 异步装饰器
def atime(fn):
@wraps(fn)
async def w(*a, **kw):
t = time.perf_counter()
try:
return await fn(*a, **kw)
finally:
print("cost:", time.perf_counter()-t)
return w
@atime
async def fetch():
await asyncio.sleep(1) # 模拟耗时操作
print('函数执行完毕')
@A
@B
def f(): ...
# 等价于 f = A(B(f))保留原函数元数据
使用 functools.wraps 不加 wraps 会让 __name__、__doc__ 等看起来像 wrapper
from functools import wraps
def greet(fn):
@wraps(fn) # 关键:保留签名/文档/名称
def wrapper(*args, **kwargs):
print(">>> before")
return fn(*args, **kwargs)
return wrapper带参数的装饰器
from functools import wraps
def deco(n): # 外层:装饰器工厂(接参数)
print("factory called with", n)
def decorator(func): # 内层:真正装饰器(接函数)
print("decorating", func.__name__)
@wraps(func)
def wrapper(*a, **kw):
print("n is", n) # 用到外层参数(闭包捕获)
return func(*a, **kw)
return wrapper
return decorator
@deco(5)
def hello():
print("hello")
# 装饰器工厂:带重试次数参数
def retry(times=3):
def deco(fn):
@wraps(fn)
def wrapper(*a, **kw):
last = None
for attempt in range(1, times + 1):
try:
return fn(*a, **kw)
except Exception as e:
last = e
# 打印每次失败信息,方便调试
print(f"[retry {attempt}/{times}] {e}")
# 所有重试都失败后,抛出最后一次异常
raise last
return wrapper
return deco
@retry(times=5)
def flaky():
import random
# 把随机种子固定,确保每次运行结果一致,方便观察重试过程
# random.seed(1)
if random.randint(0, 1) == 0:
raise ValueError("随机失败")
return "成功"类装饰器
# 方法装饰器
def log_call(fn):
@wraps(fn)
def w(self, *a, **kw):
print(f"call {fn.__name__} with", a, kw)
return fn(self, *a, **kw)
return w
class Svc:
@log_call
def run(self, x): return x*2
# 类装饰器
def add_repr(cls):
cls.__repr__ = lambda self: f"<{cls.__name__} {self.__dict__}>"
return cls
@add_repr
class Point:
def __init__(self,x,y): self.x,self.y=x,y配套代码:vsme/learn-python