
如果你曾经递给别人一个工具,而不是自己亲自动手完成某项工作,那么你已经理解了一等函数的核心思想。在 Python 中,函数就是值:你可以将它们存储在变量中、传递给其他函数、作为返回值、放入列表或字典------享受真正的"一等公民"待遇。一旦理解这一点,很多 Python 代码会突然变得更流畅、更强大。
下面是一次实用、易懂的探索之旅。我们将用简单的语言、真实的示例和明快的节奏来展开。
"一等公民"的真正含义
python
def greet(name):
return f"Hello, {name}!"
say_hello = greet # 赋值给变量
toolbox = {"hi": greet} # 存储到字典中
def loud(func, name): # 作为参数传递
return func(name).upper()
def greeter(prefix): # 返回一个函数
def inner(name):
return f"{prefix} {name}"
return inner
print(say_hello("Anik")) # Hello, Anik!
print(loud(greet, "Anik")) # HELLO, ANIK!
print(greeter("Welcome")("Anik")) # Welcome Anik
这就是一等函数的核心。本文其余内容都建立在这个基础之上。
文档字符串与类型注解:小注解,大回报
文档字符串解释为什么和怎么做,类型注解则提示输入和输出的类型。它们不会改变运行时行为,但能帮助未来的你(以及你的团队、编辑器、linter 和测试)。
python
from typing import Iterable, Callable, TypeVar, List
T = TypeVar("T")
U = TypeVar("U")
def transform(items: Iterable[T], fn: Callable[[T], U]) -> List[U]:
"""
对每个元素应用函数,并返回结果列表。
参数:
items: 任意可迭代对象(列表、元组、生成器等)
fn: 接收 T 类型参数并返回 U 类型的函数
返回:
转换后的值组成的列表
"""
return [fn(x) for x in items]
一个好习惯:如果函数行为不明显,就加上文档字符串;如果参数类型不明确,就加上类型注解。
实际案例:
当你处理来自 CSV 的用户输入时,为转换函数添加注解,这样队友就能清楚地知道数据的预期格式。
Lambda 表达式:轻巧的即时函数
Lambda 是一种小巧的单表达式函数,可以内联定义。
python
square = lambda x: x * x
print(square(5)) # 25
Lambda 适用于简短的胶水代码,特别是在排序和函数式工具中。
何时避免使用: 如果逻辑需要命名、多个步骤或文档字符串,请使用 def
。
实际案例: 移除货币符号并转换为浮点数。
python
prices = ["$12.99", "$5.50", "$100.00"]
to_number = lambda s: float(s.replace("$", ""))
clean = [to_number(p) for p in prices] # [12.99, 5.5, 100.0]
Lambda 与排序:化混乱为有序
Python 的 sorted
函数非常喜欢 key
参数,而 Lambda 在这里表现出色。
python
products = [
{"name": "Keyboard", "price": 59.99},
{"name": "Mouse", "price": 25.00},
{"name": "Monitor", "price": 199.00},
]
# 按价格升序排序
by_price = sorted(products, key=lambda p: p["price"])
# 先按名称长度排序,再按字母顺序排序
by_len_then_name = sorted(
products,
key=lambda p: (len(p["name"]), p["name"])
)
提示: 在对字典或对象排序时,为了提升速度和可读性,可以使用 operator.itemgetter
和 operator.attrgetter
(稍后会介绍)。
一个有趣的挑战:用 sorted
"随机化"一个可迭代对象(!!)
"等等,用 sorted
来打乱顺序?"没错,这是一个技巧:
python
import random
data = [1, 2, 3, 4, 5]
randomized = sorted(data, key=lambda _: random.random())
每个元素被赋予一个随机键值,根据这些键值排序后就会产生随机顺序。
说明: 这是一个取巧的方法。如果希望原地打乱列表,更推荐使用 random.shuffle(data)
,它更快、更清晰。但如果你需要非破坏性的打乱(保留原列表不变),那么 sorted(..., key=...)
的技巧就很适用。
函数内省:窥探内部机制
Python 允许你查看函数的内部信息。
python
import inspect
def price_with_tax(price: float, rate: float = 0.15) -> float:
"""返回含税价格"""
return round(price * (1 + rate), 2)
print(price_with_tax.__name__) # 'price_with_tax'
print(price_with_tax.__doc__) # 文档字符串
print(price_with_tax.__annotations__) # {'price': float, 'rate': float, 'return': float}
print(price_with_tax.__defaults__) # (0.15,)
sig = inspect.signature(price_with_tax)
print(sig) # (price: float, rate: float=0.15) -> float
for name, param in sig.parameters.items():
print(name, param.default, param.annotation)
实际用途: 构建一个小型 CLI 或 Web 路由,通过读取注解和默认值自动生成帮助文本或表单字段。
可调用对象:不仅仅是函数
任何可以用 ()
调用的对象都是可调用对象,包括函数、方法、Lambda 以及定义了 __call__
方法的对象。
python
class RateLimiter:
def __init__(self, limit_per_minute: int):
self.limit = limit_per_minute
self.calls = 0
def __call__(self) -> bool:
# 简单示例:允许前 N 次调用,之后阻止
self.calls += 1
return self.calls <= self.limit
allow = RateLimiter(3)
print(allow()) # True
print(allow()) # True
print(allow()) # True
print(allow()) # False
你可以检查对象是否可调用:
python
callable(len) # True
callable(RateLimiter) # True(类是可调用的------它们用于构造对象)
callable(allow) # True
优势: 你可以传递带有状态的行为。非常适合缓存、节流、验证器和格式化器等场景。
Map、filter、zip 与列表推导式
这些是 Python 的高效流水线工具。
map(fn, iterable)
对每个元素应用函数。
python
names = ["anik", "sara", "LEE"]
proper = list(map(str.title, names)) # ['Anik', 'Sara', 'Lee']
filter(fn, iterable)
保留使 fn(item)
为真的元素。
python
scores = [95, 50, 77, 88]
passed = list(filter(lambda s: s >= 60, scores)) # [95, 77, 88]
zip(a, b, ...)
按位置配对元素。
python
students = ["Anik", "Sara", "Lee"]
grades = [95, 88, 77]
paired = list(zip(students, grades)) # [('Anik', 95), ('Sara', 88), ('Lee', 77)]
用星号运算符解压:
python
names2, marks2 = zip(*paired)
列表推导式
通常比 map
/filter
更清晰(也更 Pythonic):
python
proper = [n.title() for n in names]
passed = [s for s in scores if s >= 60]
实际案例: 清理 CSV 行数据。
python
rows = [
{"name": " anik ", "age": "23"},
{"name": "SARA", "age": " 19 "},
]
clean_rows = [
{"name": r["name"].strip().title(), "age": int(r["age"])}
for r in rows
]
归约函数:将序列折叠为单个值
functools.reduce
累积应用函数。虽然可以用 reduce
实现很多功能,但在 Python 中我们通常更倾向于使用内置函数(如 sum
、min
、max
、any
、all
),因为它们更清晰、更快。
python
from functools import reduce
from operator import mul
nums = [2, 3, 4]
product = reduce(mul, nums, 1) # 24
计算累计值(折叠):
python
from functools import reduce
transactions = [100, -20, -5, 50]
balance = reduce(lambda acc, t: acc + t, transactions, 0) # 125
如果有更好的替代方案,请优先使用:
python
sum(transactions) # 125
any(x < 0 for x in transactions) # True
max(prices, default=0.0) # 简单明了
实际案例: 统计词频(但请注意后面的说明!)。
python
from functools import reduce
words = "to be or not to be".split()
freq = reduce(
lambda acc, w: (acc.update({w: acc.get(w, 0) + 1}) or acc,
words,
{}
)
# {'to': 2, 'be': 2, 'or': 1, 'not': 1}
说明: 这个技巧主要用于教学,实际项目中请使用 collections.Counter
,它更清晰、更快。
偏函数:预先填充参数以备后用
functools.partial
可以创建一个新函数,其中部分参数已被固定。就像一次性设置默认值,然后随处复用。
python
from functools import partial
def apply_tax(price: float, rate: float) -> float:
return round(price * (1 + rate), 2)
vat_bd = partial(apply_tax, rate=0.15) # 以孟加拉国增值税为例,约为15%
print(vat_bd(100)) # 115.0
实际案例:
-
格式化和解析:
pythonint_base2 = partial(int, base=2) print(int_base2("1011")) # 11
-
自定义舍入:
pythonround2 = partial(round, ndigits=2) print(round2(3.14159)) # 3.14
-
预配置的验证器、日志记录器或 HTTP 调用(例如
get_json = partial(requests.get, timeout=5)
),保持代码 DRY(不重复)。
operator 模块:小巧、快速、易读的助手
operator
模块提供了 Python 运算符和常见访问模式的函数版本。它们速度快,并且与 sorted
、map
和 reduce
完美配合。
python
from operator import itemgetter, attrgetter, methodcaller, add, mul
itemgetter
用于字典/元组
python
rows = [
{"city": "Dhaka", "pop": 21_000_000},
{"city": "Chattogram", "pop": 2_600_000},
]
top = max(rows, key=itemgetter("pop"))
# {'city': 'Dhaka', 'pop': 21000000}
attrgetter
用于对象
python
class User:
def __init__(self, name, age): self.name, self.age = name, age
users = [User("Anik", 23), User("Sara", 21)]
youngest = min(users, key=attrgetter("age"))
methodcaller
调用指定方法
python
caps = list(map(methodcaller("upper"), ["hi", "there"])) # ['HI', 'THERE']
算术运算符
python
from functools import reduce
total = reduce(add, [1, 2, 3, 4], 0) # 10
area_scale = reduce(mul, [2, 3, 4], 1) # 24
为何使用这些? 它们读起来像英语,避免编写微小的 Lambda,并且可能稍快一些。
常用的设计模式
策略模式(运行时选择行为):
python
strategies = {
"percent": lambda price, v: price * (1 - v),
"flat": lambda price, v: price - v,
}
def apply_discount(kind, price, value):
return strategies[kind](price, value)
管道:
python
def pipe(x, *funcs):
for f in funcs: x = f(x)
return x
result = pipe(
" hello ",
str.strip,
str.title,
lambda s: f"{s}!"
)
# 'Hello!'
事件/钩子的回调: 传递函数以便后续调用(GUI 事件、Web 钩子、重试机制)。
动手试一试(选几个试试吧!)
- 多键排序: 对学生字典列表按分数降序、姓名升序排序。使用
itemgetter
和负分数,或key=lambda s: (-s['score'], s['name'])
。 - 可调用对象: 创建一个
Slugify
类,通过__call__
方法记住允许的字符并将标题转换为 URL slugs。 - 偏函数实战: 创建
round_bd = partial(round, ndigits=0)
,并在map
中使用它来整理传感器数据。
总结
一等函数并非炫技,而是"Pythonic"代码的核心:
- 它们让你像搭乐高一样组合行为。
- 它们让代码保持 DRY、可测试和灵活。
- 它们与标准库(如
functools
、operator
、itertools
等)完美配合。
你是否有一个小巧优雅、反复使用的函数?可能是一个小小的格式化器、一个让你自豪的过滤器,或是一个简化了混乱工作流的可调用类?最好的模式往往从小处开始,却能走得很远。
欢迎在评论区分享你常用的函数式编程技巧或心得! 🚀