第七章 函数是一等对象
在编程语言理论中,一等对象(first-class object)指满足以下所有条件的程序实体:
- 可在运行时创建
- 可赋值给变量或数据结构中的元素
- 可作为参数传递给函数
- 可作为函数的返回结果
Python 中的整数、字符串、字典等都是典型的一等对象,函数同样是一等对象。
函数在 Python 中是 function 类的实例,拥有属性(如 __doc__)和行为,可像其他对象一样操作。
# 创建函数并检查其属性
def factorial(n):
"""returns n!"""
return 1 if n < 2 else n * factorial(n - 1)
# 调用函数
result = factorial(42)
print(result)
# 输出: 1405006117752879898543142606244511569936384000000000
# 访问文档字符串
# __doc__ 是函数对象的内置属性,用于存储文档字符串
doc = factorial.__doc__
print(doc)
# 输出: 'returns n!'
# 检查类型
t = type(factorial)
print(t)
# 输出: <class 'function'>
# 将函数赋值给变量
fact = factorial
print(fact) # <function factorial at 0x...>
print(fact(5)) # 120
# 将函数作为参数传递给 map()
mapped = map(factorial, range(11))
result_list = list(mapped)
print(result_list)
# 输出: [1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800]
高阶函数
高阶函数(higher-order function)是指满足以下任一条件的函数:
- 接受函数作为参数
- 返回函数作为结果
Python 中的 map 和 sorted 都是典型的高阶函数。
fruits = ['strawberry', 'fig', 'apple', 'cherry', 'raspberry', 'banana']
# sorted() 的 key 参数接受一个单参数函数
result = sorted(fruits, key=len)
print(result)
# 输出: ['fig', 'apple', 'cherry', 'banana', 'raspberry', 'strawberry']
# 反向拼写
# reverse 函数返回单词的逆序;
def reverse(word):
return word[::-1]
# 'strawberry' → 'yrrebwarts'
# 'fig' → 'gif'
# 'apple' → 'elppa'
# 'cherry' → 'yrrehc'
# 'raspberry' → 'yrrebpsar'
#'banana' → 'ananab'
fruits = ['strawberry', 'fig', 'apple', 'cherry', 'raspberry', 'banana']
result = sorted(fruits, key=reverse)
print(result)
# #'banana' → 'ananab' 所以banana在第一个 有相同后缀的各种~berry被放在了一起
# 输出: ['banana', 'apple', 'fig', 'raspberry', 'strawberry', 'cherry']
替代内置函数
Python 3 仍保留 map 和 filter 作为内置函数,但自引入列表推导式(list comprehensions)和生成器表达式(generator expressions)后,它们的使用频率显著下降。这些新语法不仅能同时实现 map 和 filter 的功能,而且更具可读性。
from math import factorial # 假设 factorial 已定义或从 math 导入(注意:math.factorial 仅接受整数)
# 使用 map 生成 0! 到 5!
result1 = list(map(factorial, range(6)))
print(result1)
# 输出: [1, 1, 2, 6, 24, 120]
# 使用列表推导式实现相同功能
# 列表递推式 [表达式 for 变量 in 可迭代对象 if 条件]
result2 = [factorial(n) for n in range(6)]
print(result2)
# 输出: [1, 1, 2, 6, 24, 120]
# 使用 map + filter:仅对奇数 n 计算阶乘
# `n % 2` 等价于 `n % 2 != 0`,用于判断奇数。
result3 = list(map(factorial, filter(lambda n: n % 2, range(6))))
print(result3)
# 输出: [1, 6, 120]
# 列表推导式同时完成映射和过滤,无需 lambda
result4 = [factorial(n) for n in range(6) if n % 2]
print(result4)
# 输出: [1, 6, 120]
在 Python 3 中,map 和 filter 返回迭代器,因此需用 list() 包裹才能显示完整结果;其最自然的替代是生成器表达式(如 (factorial(n) for n in range(6)))。
reduce
from functools import reduce
from operator import add
# 使用 reduce 求和
total1 = reduce(add, range(100))
print(total1)
# 输出: 4950
# 使用 sum 求和(更简洁、高效)
total2 = sum(range(100))
print(total2)
# 输出: 4950
规约
Python 提供了多个用于“归约”(将序列缩减为单个布尔值或数值)的内置函数:
-
all(iterable)
当可迭代对象中所有元素为真(或为空)时返回True。
示例:all([1, 2, 3]) → True,all([1, 0, 3]) → False,all([]) → True -
any(iterable)
当可迭代对象中存在至少一个真值元素时返回True;空序列返回False。
示例:any([0, 0, 1]) → True,any([]) → False
这些函数体现了“归约”思想:通过累积操作将序列压缩为单一结果。
匿名函数
Python 使用 lambda 关键字在表达式内部创建匿名函数。
lambda函数体只能包含表达式,不能包含语句(如while、try、=赋值等)。- 虽然 Python 3.8 引入的“海象运算符”(
:=)允许在lambda中使用赋值表达式,但若需使用它,通常说明该lambda已过于复杂,应改用def定义的命名函数。
lambda 最适合用作高阶函数的参数,尤其在函数逻辑简单、仅需一行表达式时。
# 使用 lambda 按单词反向拼写排序
# 避免了单独定义 reverse 函数
fruits = ['strawberry', 'fig', 'apple', 'cherry', 'raspberry', 'banana']
result = sorted(fruits, key=lambda word: word[::-1])
print(result)
# 输出: ['banana', 'apple', 'fig', 'raspberry', 'strawberry', 'cherry']
可调用对象
在 Python 中,可调用对象(callable objects)是指能使用调用运算符 () 的对象。判断一个对象是否可调用,应使用内置函数 callable()。
1.用户定义的函数(User-defined functions)
通过 def 语句或 lambda 表达式创建。
2. 内置函数(Built-in functions)
由 C 实现(以 CPython 为例),例如 len、time.strftime。
3. 内置方法(Built-in methods)
由 C 实现的绑定方法,例如 dict.get。
4. 方法(Methods)
在类中定义的函数,通过实例或类访问时自动绑定。
5. 类(Classes)
调用类会触发对象创建流程:
- 先调用
__new__创建实例; - 再调用
__init__初始化; - 最终返回新实例。
6. 类实例(Class instances)
若类定义了 __call__ 方法,则其实例可被调用,行为如同函数。
7. 生成器函数(Generator functions)
函数体中包含 yield 关键字。调用时返回生成器对象(一种迭代器),而非直接返回数据。
8. 原生协程函数(Native coroutine functions)
使用 async def 定义。调用时返回协程对象,需配合 await 和异步框架(如 asyncio)使用。
9. 异步生成器函数(Asynchronous generator functions)
使用 async def 定义且包含 yield。调用时返回异步生成器,用于 async for 循环。
注意:生成器函数、协程函数和异步生成器函数的返回值不是应用程序数据本身,而是需要进一步处理的对象(如迭代器或协程)。
callable()
由于可调用对象类型多样,最可靠的方式是使用 callable():
abs_func = abs
str_class = str
not_callable = 'Ni!'
# abs 是内置函数 → 可调用;
# str 是类 → 可调用;
# 'Ni!' 是字符串 → 不可调用。
results = [callable(obj) for obj in (abs_func, str_class, not_callable)]
print(results)
# 输出: [True, True, False]
自定义可调用类型
在 Python 中,任意对象都可以表现得像函数一样,只需在其类中实现 __call__ 实例方法。
import random
class BingoCage:
def __init__(self, items):
# 创建本地副本,避免副作用
# 只要初始项唯一,所有返回结果就唯一
self._items = list(items)
# 随机打乱内部列表
random.shuffle(self._items)
def pick(self):
# 主要方法:弹出一个项
try:
# BingoCage不会重复返回同一项,因为每次调用会从内部列表中移除已选元素。
return self._items.pop()
except IndexError:
raise LookupError('pick from empty BingoCage')
def __call__(self):
# 使实例可调用
return self.pick()
bingo = BingoCage(range(3))
# 显式调用 pick()
first = bingo.pick()
print(first) # 例如: 1
# 通过 __call__ 隐式调用
second = bingo()
print(second) # 例如: 0
# 验证可调用性
print(callable(bingo)) # 输出: True
实现 __call__ 是创建具有内部状态的类函数对象的便捷方式。状态(如 BingoCage 中剩余的项)可在多次调用间保持。典型应用场景包括装饰器(decorator):装饰器必须可调用,且常需在调用间“记住”信息(如缓存结果,即记忆化);将复杂逻辑拆分为多个方法,同时对外提供简洁的函数式接口。
函数参数机制
Python 函数支持多种参数传递方式,配合 * 和 ** 解包语法,可实现高度灵活的 API 设计。
def func(pos_only, pos_or_kwd, *, kwd_only, **kwargs):
...
| 区域 | 说明 |
|---|---|
pos_only | 仅限位置参数 |
/ | 分隔符:其左侧参数只能通过位置传入 |
pos_or_kwd | 位置或关键字参数 |
* | 分隔符:其右侧参数只能通过关键字传入 |
kwd_only | 仅限关键字参数 |
**kwargs | 捕获额外关键字参数 |
仅限关键字参数
将参数放在 * 或 *args 之后:
def f(a, *, b):
return a, b
# 或
def tag(name, *content, class_=None, **attrs):
...
必须通过 关键字 传参(如 b=2),不能作为位置参数。可带默认值(可选),也可不带(必填)。若不需要 *args,可用单独的 * 作为分隔符。
>>> f(1, b=2)
(1, 2)
>>> f(1, 2)
TypeError: f() takes 1 positional argument but 2 were given
def tag(name, *content, class_=None, **attrs):
if class_ is not None:
attrs['class'] = class_ # 避免使用保留字 `class`
attr_str = ''.join(f' {k}="{v}"' for k, v in sorted(attrs.items()))
if content:
return '\n'.join(f'<{name}{attr_str}>{c}</{name}>' for c in content)
else:
return f'<{name}{attr_str} />'
tag('br') # '<br />'
tag('p', 'hello') # '<p>hello</p>'
tag('p', 'hello', id=33) # '<p id="33">hello</p>'
tag('p', 'hello', 'world', class_='sidebar') # 两个带 class 的段落
tag(name='img', content='test') # name 可关键字传入(当前未限制)
tag(**{'name': 'img', 'class': 'framed'}) # 字典解包,class 作为字符串无冲突
仅限位置参数
在参数列表中使用 /,其左侧参数为仅限位置参数:
def divmod(a, b, /):
return a // b, a % b
def tag(name, /, *content, class_=None, **attrs):
...
/ 左侧参数 不能 通过关键字传入。有助于模拟 C 扩展函数行为(如 len(obj=...) 不合法)。允许未来重命名参数而不影响调用代码。参数名不会与 **kwargs 冲突(因不会被关键字绑定)。
>>> divmod(10, 3)
(3, 1)
>>> divmod(a=10, b=3)
TypeError: divmod() takes no keyword arguments
若修改 tag 函数:
def tag(name, /, *content, class_=None, **attrs): ...
则:
tag('img') # 合法
tag(name='img') # TypeError: 'name' is positional-only
def func(
pos_only1, pos_only2, /, # 仅限位置
pos_or_kwd1, pos_or_kwd2=None, # 位置或关键字
*args, # 可变位置参数
kwd_only1, kwd_only2='default', # 仅限关键字
**kwargs # 可变关键字参数
):
...
顺序不可乱:pos-only → / → pos-or-kwd → *args → kwd-only → **kwargs
函数式编程
operator 模块
operator 模块为 Python 的内置运算符提供了对应的函数形式,避免编写琐碎的 lambda 表达式。
mul
使用 operator.mul 替代 lambda a, b: a * b 实现阶乘:
from functools import reduce
from operator import mul
def factorial(n):
# 使用reduce进行累乘 从而实现阶乘的计算
return reduce(mul, range(1, n + 1))
itemgetter:提取序列或映射中的元素
接收一个或多个索引(或键),返回一个可调用对象。常用于排序等场景。
metro_data = [
('Tokyo', 'JP', 36.933, (35.689722, 139.691667)),
('Delhi NCR', 'IN', 21.935, (28.613889, 77.208889)),
('Mexico City', 'MX', 20.142, (19.433333, -99.133333)),
('New York-Newark', 'US', 20.104, (40.808611, -74.020386)),
('São Paulo', 'BR', 19.649, (-23.547778, -46.635833)),
]
from operator import itemgetter
# 按国家代码(索引 1)排序
for city in sorted(metro_data, key=itemgetter(1)):
print(city)
# 实际输出:
# 国家代码顺序:'BR' < 'IN' < 'JP' < 'MX' < 'US'
# ('São Paulo', 'BR', 19.649, (-23.547778, -46.635833))
# ('Delhi NCR', 'IN', 21.935, (28.613889, 77.208889))
# ('Tokyo', 'JP', 36.933, (35.689722, 139.691667))
# ('Mexico City', 'MX', 20.142, (19.433333, -99.133333))
# ('New York-Newark', 'US', 20.104, (40.808611, -74.020386))
# 多字段提取(用于多级排序)
# # itemgetter(1, 0) 返回每个元组的 (国家代码, 城市名)
cc_name = itemgetter(1, 0)
for city in metro_data:
print(cc_name(city))
itemgetter 基于 __getitem__,因此适用于列表、元组、字典等。
attrgetter:提取对象属性
支持嵌套属性(通过点号 . 访问)。可提取多个属性,返回元组。
from collections import namedtuple
from operator import attrgetter
# 定义嵌套的命名元组结构
LatLon = namedtuple('LatLon', 'lat lon') # 表示经纬度
Metropolis = namedtuple('Metropolis', 'name cc pop coord') # 表示大都市信息
metro_data = [
('Tokyo', 'JP', 36.933, (35.689722, 139.691667)),
('Delhi NCR', 'IN', 21.935, (28.613889, 77.208889)),
('Mexico City', 'MX', 20.142, (19.433333, -99.133333)),
('New York-Newark', 'US', 20.104, (40.808611, -74.020386)),
('São Paulo', 'BR', 19.649, (-23.547778, -46.635833)),
]
metro_areas = [Metropolis(name, cc, pop, LatLon(lat, lon))
for name, cc, pop, (lat, lon) in metro_data]
# # 按 coord.lat(纬度)升序排序
# 纬度值从小到大:-23.547778 (São Paulo) < 19.433333 (Mexico City) < 28.613889 (Delhi) < 35.689722 (Tokyo) < 40.808611 (New York)
for city in sorted(metro_areas, key=attrgetter('coord.lat')):
print(attrgetter('name', 'coord.lat')(city))
methodcaller:调用对象方法
- 创建一个可调用对象,用于调用指定方法。
- 支持预绑定方法参数(类似部分应用)。
from operator import methodcaller
s = 'The time has come'
upcase = methodcaller('upper')
print(upcase(s)) # 'THE TIME HAS COME'
hyphenate = methodcaller('replace', ' ', '-')
print(hyphenate(s)) # 'The-time-has-come'
注意:若只需调用无参方法(如 str.upper),可直接使用 str.upper(s),无需 methodcaller。
functools.partial:固定部分参数
functools.partial 用于创建新函数,其中原始函数的部分参数已被预先绑定。
# 基本用法
from operator import mul
from functools import partial
triple = partial(mul, 3) # 固定第一个参数为 3
print(triple(7)) # 21
print(list(map(triple, range(1, 10))))
# [3, 6, 9, 12, 15, 18, 21, 24, 27]
# 应用于 Unicode 标准化
import unicodedata
import functools
nfc = functools.partial(unicodedata.normalize, 'NFC')
s1 = 'café'
s2 = 'cafe\u0301' # 组合字符形式
print(s1 == s2) # False
print(nfc(s1) == nfc(s2)) # True
# 应用于 tag 函数
from functools import partial
# 假设 tag 函数已定义(见前文)
picture = partial(tag, 'img', class_='pic-frame')
print(picture(src='wumpus.jpeg'))
# '<img class="pic-frame" src="wumpus.jpeg" />'
# 查看 partial 对象内部
print(picture.func) # <function tag at ...>
print(picture.args) # ('img',)
print(picture.keywords) # {'class_': 'pic-frame'}
438

被折叠的 条评论
为什么被折叠?



