news 2026/10/30 5:12:34

简析Python函数式编程字符串和元组及函数分类与高阶函数

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
简析Python函数式编程字符串和元组及函数分类与高阶函数

前言


这个标题把好几组概念并排列在一起,其实它们处在不同层次上,需要先厘清:「函数式编程」是一种编程风格,「字符串和元组」是两种具体的数据类型,「函数分类」和「高阶函数」则是围绕函数这个对象展开的话题。把它们塞进一句标题,是关键词堆出来的,读起来像并列关系,实际不是。本文按真实的概念层次来组织:先讲函数在 Python 里是什么、怎么分类,再讲高阶函数,最后讲为什么字符串和元组天然适合函数式风格。


先说最关键的一点:在 Python 里,函数是「一等对象」(first-class object),和整数、字符串、列表没有本质区别。函数可以被赋值给变量、作为参数传给别的函数、作为返回值返回、放进列表和字典。这不是什么高级技巧,而是理解 Python 一切函数式写法(map、filter、sorted(key=...)、装饰器)的地基。


常见的误解是把「函数式编程」等同于「用map和filter」。其实函数式风格的核心约束是避免副作用、不做原地修改、用纯函数把输入映射成输出。Python 不是纯函数式语言,它允许list.append、允许赋值,所以这里的「函数式」更多是一种倾向:能用不可变对象就不用可变的,能用表达式表达就不写循环。


一、函数是什么:一等对象的四个证据


# 适用于 Python 3.8+
def double(x):
return x * 2

f = double # 1. 赋值给变量
print(f(3)) # 6

funcs = {"double": double, "upper": str.upper} # 2. 放进容器
print(funcs["double"](3)) # 6
print(funcs["upper"]("ab")) # AB

def apply_twice(func, value): # 3. 作为参数
return func(func(value))

def make_adder(n): # 4. 作为返回值(闭包)
def adder(x):
return x + n
return adder

print(apply_twice(double, 3)) # 12
add5 = make_adder(5)
print(add5(10)) # 15

make_adder返回的adder记住了外层的n,这就是闭包(closure)——函数式风格的常见构件。


二、函数的分类


按「怎么产生」和「用在哪」,Python 的函数可以分成几类:




类别例子说明



内置函数(built-in function)len、print、abs由解释器提供,C 实现

自定义函数def double(...)用def定义

匿名函数(lambda)lambda x: x * 2只能是单个表达式

方法(method)"a".upper、lst.append绑定到对象,带self

高阶函数(higher-order function)sorted、map、filter接收函数作为参数或返回函数

可调用对象(callable)实现了__call__的实例像函数一样obj()



lambda与def的关键差别:lambda 的函数体只能是一个表达式,不能写if/else语句、不能写多行、不能有文档字符串。它适合「就地传一个小函数」,不适合承载复杂逻辑。


# 适用于 Python 3.8+
print((lambda a, b: a + b)(2, 3)) # 5,立即调用
words = ["bb", "a", "ccc"]
print(sorted(words, key=lambda w: len(w))) # ['a', 'bb', 'ccc']

三、高阶函数:把函数当参数


高阶函数的定义是「接收函数作为参数,或返回一个函数」。日常最常用的其实不是map,而是sorted的key:


# 适用于 Python 3.8+
records = [("alice", 30), ("bob", 25), ("carol", 28)]
print(sorted(records, key=lambda r: r[1])) # 按年龄升序
print(sorted(records, key=lambda r: r[1], reverse=True)) # 降序

# 适用于 Python 3.8+
nums = [1, 2, 3, 4, 5]

squares = map(lambda x: x * x, nums) # 返回迭代器,惰性
evens = filter(lambda x: x % 2 == 0, nums) # 同样返回迭代器
print(list(squares)) # [1, 4, 9, 16, 25]
print(list(evens)) # [2, 4]

重要版本差异:在 Python 2 里map和filter返回列表,在 Python 3 里它们返回迭代器。这意味着两件事:结果只遍历一次就空了;想拿到列表必须显式list(...)。这也是 Python 2 代码迁移时常见的隐性 bug。


functools.reduce把二元函数累积地作用到序列上:


# 适用于 Python 3.8+
from functools import reduce

print(reduce(lambda a, b: a * b, [1, 2, 3, 4])) # 24
print(reduce(lambda a, b: a + b, [1, 2, 3], 100)) # 106,100 是初始值

注意reduce在 Python 3 里不再是内置函数,必须从functools导入(Python 2 时代它是内置的)。


functools.partial用来「预先固定一部分参数」,生成一个新的可调用对象:


# 适用于 Python 3.8+
from functools import partial

def power(base, exp):
return base ** exp

square = partial(power, exp=2)
print(square(5)) # 25

四、字符串和元组:为什么它们契合函数式风格


函数式风格偏好不可变(immutable)的数据,因为不可变对象不会被「别人偷偷改掉」,写出来的函数没有副作用。Python 的两个内建不可变序列正好是字符串(str)和元组(tuple)。


# 适用于 Python 3.8+
s = "hello"
t = (1, 2, 3)

# 所有「修改」操作其实都返回新对象,原对象不变
s2 = s.upper()
t2 = t + (4,)
print(s, "|", s2) # hello | HELLO
print(t, "|", t2) # (1, 2, 3) | (1, 2, 3, 4)

# s[0] = "H" # TypeError: 'str' object does not support item assignment
# t[0] = 9 # TypeError: 'tuple' object does not support item assignment

不可变带来两个直接好处。第一,可以安全地当字典的键(前提是元组里的每个元素本身可哈希),str和tuple都能做键,而list不能:


# 适用于 Python 3.8+
grid = {("a", 1): "x"}
print(grid[("a", 1)]) # x

第二,适合做函数式变换的载体:把元组当成「不可变的记录」,用map生成一串新元组,而不用担心数据被下游改掉。


元组还支持解包(unpacking),这让它在小规模数据的函数式处理里非常好用:


# 适用于 Python 3.8+
point = (3, 4)
x, y = point
print(x + y) # 7

pairs = [("a", 1), ("b", 2)]
print([f"{k}={v}" for k, v in pairs]) # ['a=1', 'b=2']

需要「带名字的元组」时用collections.namedtuple,它既有元组的不可变和低开销,又能按字段名访问:


# 适用于 Python 3.8+
from collections import namedtuple

Point = namedtuple("Point", ["x", "y"])
p = Point(3, 4)
print(p.x, p.y) # 3 4
print(p._replace(y=10)) # Point(x=3, y=10),返回新对象

五、实战:用函数式风格做数据流水线


把上面的零件组合起来:给一批记录打分、排序、取名,全程不修改原数据。


# 适用于 Python 3.8+
from collections import namedtuple

Student = namedtuple("Student", ["name", "score"])

raw = [("alice", 88), ("bob", 72), ("carol", 95), ("dave", 72)]

# 1. 转成不可变记录
students = [Student(*item) for item in raw]

# 2. 过滤 + 排序(都返回新对象,不改 students)
passed = list(filter(lambda s: s.score >= 80, students))
ranked = sorted(passed, key=lambda s: s.score, reverse=True)

# 3. 生成展示名(str 操作同样返回新字符串)
labels = list(map(lambda s: f"{s.name.title()}({s.score})", ranked))

print(labels) # ['Carol(95)', 'Alice(88)']
print(students[0]) # Student(name='alice', score=88) —— 原数据未变

整段代码没有一个for循环型的原地修改,每个环节都输入旧数据、输出新数据——这就是函数式风格在 Python 里的实用形态。


常见坑点



  1. 以为 map / filter 返回列表


❌n = map(int, ["1", "2"]); print(len(n))→TypeError: object of type 'map' has no len()


✅ Python 3 里它们是惰性迭代器:需要列表用list(map(...));且只能遍历一次



  1. 把 reduce 当内置函数


❌reduce(lambda a, b: a + b, [1, 2])→NameError: name 'reduce' is not defined


✅ Python 3 里要从functools导入:from functools import reduce



  1. 循环里用 lambda 造成延迟绑定


❌funcs = [lambda: i for i in range(3)],调用时三个都返回2


✅ 用默认参数固定当前值:[lambda i=i: i for i in range(3)]



  1. 用 sort() 的返回值


❌result = lst.sort()得到None,因为list.sort()原地排序并返回None


✅ 要新列表用sorted(lst)(返回新列表);要原地排用lst.sort()且不要接返回值



  1. 误以为字符串 / 元组的方法会改自己


❌s.strip()之后直接用s,发现没变;t.append(1)直接AttributeError


✅ 接住返回值:s = s.strip();元组本就没有 append,要扩展得写t = t + (1,)



  1. 用可变默认参数配函数式风格


❌def collect(x, acc=[]): acc.append(x); return acc,多次调用之间互相污染


✅ 默认值用不可变:def collect(x, acc=None): acc = [] if acc is None else acc; ...



  1. 误用 list 当字典键


❌d[[1, 2]] = "x"→TypeError: unhashable type: 'list'


✅ 改用元组d[(1, 2)] = "x"(元素必须都可哈希)



  1. 对迭代器重复遍历


❌it = map(str, nums)先用list(it)打印,再sum(it)得到 0


✅ 迭代器一次性消费,需要复用就转成列表存下来


总结




概念要点



一等对象函数可赋值、传参、返回、入容器

lambda只能写单个表达式,适合就地传小函数

高阶函数接收函数或返回函数,如sorted(key=...)、map、filter

map / filterPython 3 返回惰性迭代器,不是列表

reduce在functools里,不是内置

字符串不可变序列,所有方法返回新串

元组不可变序列,可哈希、可解包、适合做记录

函数式风格无副作用、不原地改、纯函数映射



把这篇的线索收成一句:函数在 Python 里是普通对象,所以能当参数和返回值;而不可变的字符串和元组,正好提供了函数式变换最省心的载体。map、filter只是工具,真正要建立的习惯是「先想这一步会不会改到现有数据」,习惯用返回新对象的方式表达变换,代码的自然副作用就会少很多。




版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/30 5:12:27

ESXi 7.0 裸金属安装实战:从镜像制作到网络配置的完整指南

简介:面向服务器运维人员与虚拟化初学者的实操型资料,聚焦 VMware vSphere ESXi 7.0 的裸金属 hypervisor 部署与基础配置,帮助读者在单台物理服务器上完成虚拟化环境的从零搭建,解决安装介质制作、存储选择、密码策略与网络配置等…

作者头像 李华
网站建设 2026/10/30 5:11:41

简单谈谈Python中的模块导入

前言 import 是学 Python 最早接触的语句之一,但它恰恰是被「会用」和「懂」拉开距离的一条。多数人对它的理解停留在「把那个文件引进来」,于是遇到下面这些情况就卡住:明明改了代码为什么没生效、为什么同样的导入在脚本里能用、放进包里就…

作者头像 李华
网站建设 2026/10/30 5:10:41

从155.520 Mb/s到63路2M:SDH帧结构与复用原理

简介:面向华为HCIA-Transmission认证备考者及传输网络初学者,这份培训课程PDF系统梳理光传送网基础与主流传输体制。内容从光传输理论演进切入,串联PDH、SDH、DWDM、OTN、PTN与RTN等应用场景,重点讲解SDH的技术背景、PDH局限&…

作者头像 李华
网站建设 2026/10/30 5:08:57

奇安信天擎V10.0部署实战:环境准备、安装配置与避坑指南

简介:奇安信天擎终端安全管理系统管理员手册面向企业终端安全运维人员与系统管理员,帮助其系统掌握天擎V10.0的部署、配置与日常管理方法,解决终端防护策略落地与安全运维中的实际问题。资源包共1个文件,为PDF格式的官方管理员手册…

作者头像 李华
网站建设 2026/10/30 5:07:57

iOS与安卓双端通讯录源码拆解:从编译到二次开发实战

简介:这份资源是面向移动端开发学习者的双端通讯录类项目源码合集,涵盖 iOS 与安卓两端 APP 及配套 PHP 后台,适合想研究通讯录、相册、短信、定位等模块实现思路的开发者参考。压缩包共约 2000 个文件、298.59MB,以 548 个 php 后…

作者头像 李华
网站建设 2026/10/30 5:07:41

可直接Play的Unity3D坦克大战工程:Rigidbody操控与状态机AI

简介:面向Unity3D入门与进阶学习者的坦克大战完整工程源码,基于经典教程案例整理完善,可直接在编辑器中运行与拆解,适合想通过小型项目掌握游戏开发主流程的开发者。压缩包共2154个文件,约24.78MB,以info、…

作者头像 李华