函数设计与 lambda
写过几章代码后你大概已经察觉:同一段计算逻辑,常常在好几个地方反复出现。函数就是解决这个问题的——把一段逻辑起个名字封装起来,哪里要用就喊一声名字。代码不再重复,读起来也更清爽
把一段逻辑装进"盒子"里
写过几章代码后你大概已经察觉:同一段计算逻辑,常常在好几个地方反复出现。函数就是解决这个问题的——把一段逻辑起个名字封装起来,哪里要用就喊一声名字。代码不再重复,读起来也更清爽。
用 def 关键字定义函数:
# 定义一个最简单的函数
def brew():
print("咖啡冲好了,请慢用~")
# 调用它
brew() # 咖啡冲好了,请慢用~
传参的五种方式
按位置对号入座
调用时实参按先后顺序一一对应到形参上:
def order_total(unit_price, count):
return unit_price * count
pay = order_total(25, 4)
print(pay) # 100
用名字指定:关键字参数
按参数名传值,谁先谁后无所谓:
def car_profile(brand, price, color):
print(f"{color}的{brand},指导价 {price} 万")
car_profile(price=20, color="红色", brand="劲风") # 红色的劲风,指导价 20 万
和位置参数混用时:位置参数必须排在关键字参数前面。
car_profile("劲风", price=20, color="红色") # 正确
# car_profile(brand="劲风", 20, "红色") # 不行!位置参数不能落在关键字参数后面
提前备好默认值
定义时给参数一个默认值,调用时可以不传:
def serve(kind, size="中杯", ice="少冰"):
print(f"{size}{kind},{ice}")
serve("拿铁") # 中杯拿铁,少冰
serve("美式", "大杯") # 大杯美式,少冰
注意: 带默认值的参数必须排在没有默认值的参数后面。
# 这样写直接语法报错
# def serve(size="中杯", kind): # SyntaxError!
# pass
来者不拒:*args
不确定调用方会塞多少个位置参数时,用 *args 全盘接收:
def exam_total(*scores):
total = 0
for s in scores:
total += s
return total
print(exam_total(80, 92, 75)) # 247
print(exam_total(66, 71, 88, 90)) # 315
print(exam_total()) # 0
args 的真身: 在函数内部,它就是一个元组。
def peek_args(*scores):
print(type(scores)) # <class 'tuple'>
print(scores) # (80, 92, 75)
peek_args(80, 92, 75)
一切收进字典:**kwargs
不确定会收到哪些关键字参数时,用 **kwargs 接住:
def show_laptop(**specs):
for part, detail in specs.items():
print(f"{part}: {detail}")
show_laptop(cpu="M3", ram="16G", disk="512G")
# cpu: M3
# ram: 16G
# disk: 512G
kwargs 的真身: 在函数内部,它就是一个字典。
def peek_kwargs(**specs):
print(type(specs)) # <class 'dict'>
print(specs) # {'cpu': 'M3', 'ram': '16G'}
peek_kwargs(cpu="M3", ram="16G")
完整的参数排列顺序
定义函数时,各类参数必须按下面的次序摆放:
def func(位置参数, 默认参数, *args, **kwargs):
pass
# 综合示例
def combo(a, b=2, *rest, **opts):
print(f"a={a}, b={b}")
print(f"rest={rest}")
print(f"opts={opts}")
combo(1, 3, 4, 5, x=10, y=20)
# a=1, b=3
# rest=(4, 5)
# opts={'x': 10, 'y': 20}
返回值的三个细节
用 return 把结果递出去
def circle_area(r):
return 3.14 * r ** 2
area = circle_area(3)
print(area) # 28.26
一次返回多个值
Python 函数可以同时交出多个结果,本质上是打包成了一个元组:
def hi_lo(temps):
return min(temps), max(temps)
low, high = hi_lo([18, 27, 33, 21])
print(low) # 18
print(high) # 33
# 只用一个变量接,接到的是整个元组
both = hi_lo([18, 27, 33])
print(both) # (18, 33)
print(type(both)) # <class 'tuple'>
不写 return,返回的是 None
def beep():
print("滴——")
noise = beep() # 滴——
print(noise) # None
给函数挂一块说明书(Docstring)
函数体开头放一个三引号字符串,就是它的说明文档:
def delivery_fee(distance, weight):
"""
估算快递运费。
参数:
distance: 配送距离,单位公里
weight: 包裹重量,单位千克
返回:
预计运费(元)
"""
return 8 + distance * 0.5 + weight * 1.2
# 查看文档
print(delivery_fee.__doc__)
lambda:随手写一个"一次性"函数
lambda 表达式用来创建匿名函数——没有名字、写完就用、用完就扔。当某个逻辑特别简单、又只在一个地方用到时,它能省下不少笔墨。
语法长这样
lambda 参数1, 参数2, ... : 表达式
# 常规写法
def add_tax(price):
return price * 1.13
# 等价的 lambda 写法
add_tax_l = lambda price: price * 1.13
print(add_tax(200)) # 226.0
print(add_tax_l(200)) # 226.0
三条规矩:
- 冒号右边只能是一个表达式,写不了多条语句
- 表达式的计算结果自动作为返回值,不用写 return
- 一般不给它起名字,现场定义现场用
lambda 还是 def?一张表看懂
| 对比项 | lambda | 普通函数(def) |
|---|---|---|
| 名字 | 匿名,通常不命名 | 必须有函数名 |
| 函数体 | 仅限一个表达式 | 可以写任意多条语句 |
| 返回值 | 表达式结果自动返回 | 需要显式写 return |
| 适合场景 | 简单、一次性 | 复杂、要反复调用 |
选择原则: 一句话能说清、用完就扔 → 用 lambda;步骤多、或者多处要复用 → 老老实实 def。
高阶函数:把函数当参数传来传去
能接收函数作为参数的函数,叫高阶函数——这正是 lambda 大显身手的舞台。
map() —— 批量加工
对可迭代对象里的每个元素套用同一个操作,返回一个迭代器:
prices = [30, 45, 128, 66]
# 先定义再传入,可行但啰嗦
def half(p):
return p / 2
result = map(half, prices)
print(list(result)) # [15.0, 22.5, 64.0, 33.0]
# 配合 lambda,一行到位
result = map(lambda p: p / 2, prices)
print(list(result)) # [15.0, 22.5, 64.0, 33.0]
map() 的语义: 给每个元素做一次"转换"。
# 歌名列表 → 歌名长度列表
songs = ["稻香", "晴天", "七里香"]
lens = map(lambda s: len(s), songs)
print(list(lens)) # [2, 2, 3]
# 两列成绩逐位相加
chinese = [86, 92, 78]
math = [90, 85, 88]
totals = map(lambda c, m: c + m, chinese, math)
print(list(totals)) # [176, 177, 166]
filter() —— 按条件筛
只留下让条件成立的那些元素:
scores = [59, 61, 88, 45, 73, 100]
# 留下及格的
passed = filter(lambda s: s >= 60, scores)
print(list(passed)) # [61, 88, 73, 100]
# 留下长度超过 2 的单词
words = ["ok", "filter", "go", "lambda"]
long_ones = filter(lambda w: len(w) > 2, words)
print(list(long_ones)) # ['filter', 'lambda']
filter() 的语义: 给元素做一次"体检",达标的留下。
# 留下正数
changes = [-3, 5, -1, 0, 2]
ups = filter(lambda x: x > 0, changes)
print(list(ups)) # [5, 2]
sorted() 的 key —— 自定义排序依据
sorted() 和列表的 .sort() 都接受 key 参数,用来回答"按什么排":
cities = ["Chengdu", "Beijing", "Shanghai", "Xiamen"]
# 按长度排(短的在前)
by_len = sorted(cities, key=lambda s: len(s))
print(by_len) # ['Xiamen', 'Chengdu', 'Beijing', 'Shanghai']
# 按末尾字母排
by_tail = sorted(cities, key=lambda s: s[-1])
print(by_tail) # ['Beijing', 'Shanghai', 'Xiamen', 'Chengdu']
# 按长度降序
by_len_desc = sorted(cities, key=lambda s: len(s), reverse=True)
print(by_len_desc) # ['Shanghai', 'Chengdu', 'Beijing', 'Xiamen']
# 按绝对值排,不看正负
swings = [-120, 85, -60, 210, -15]
by_abs = sorted(swings, key=lambda x: abs(x))
print(by_abs) # [-15, -60, 85, -120, 210]
max() / min() 的 key —— 按规则找冠军
cities = ["Chengdu", "Beijing", "Shanghai", "Xiamen"]
# 最长的城市名
print(max(cities, key=lambda s: len(s))) # Shanghai
# 最短的城市名
print(min(cities, key=lambda s: len(s))) # Xiamen
players = [
{"nick": "阿风", "kills": 12},
{"nick": "小雨", "kills": 9},
{"nick": "大山", "kills": 15},
]
# 击杀数最高的选手
mvp = max(players, key=lambda p: p["kills"])
print(mvp) # {'nick': '大山', 'kills': 15}
reduce() —— 一路折叠成一个值
reduce() 藏在 functools 模块里,作用是把序列反复"两两合并",最终压成一个值:
from functools import reduce
fees = [12, 35, 8, 60]
# 累加求和
total = reduce(lambda a, b: a + b, fees)
print(total) # 115
# 连乘
product = reduce(lambda a, b: a * b, [1, 2, 3, 4, 5])
print(product) # 120
# 手工版求最大值
peak = reduce(lambda a, b: a if a > b else b, fees)
print(peak) # 60
练习
练习一至练习四聚焦函数的定义、参数传递与返回值;练习五综合运用 lambda 表达式与内置高阶函数。
练习一:默认参数、*args 与列表联动
逐行阅读下面的代码,先在纸上写出每个 print 的输出,再运行验证。
def add_tasks(first, tasks=None, *others):
if tasks is None:
tasks = []
tasks.append(first)
for item in others:
tasks.append(item)
return tasks
list1 = add_tasks("开晨会")
print(list1)
list2 = add_tasks("写周报", ["复盘", "面试"], "报销", "团建")
print(list2)
print(list1)
练习二:**kwargs 与字典联动
逐行阅读下面的代码,先在纸上写出每个 print 的输出,再运行验证。
def restock(shelf, **incoming):
result = shelf.copy() # 浅拷贝
for name, value in incoming.items():
if name in result:
result[name] += value
else:
result[name] = value
return result
shelf = {"pen": 10, "notebook": [3, 4]}
filled = restock(shelf, pen=6, notebook=[7], ruler="缺货")
print(filled)
print(len(filled))
print(filled.get("eraser", "没找到"))
练习三:多返回值与容器遍历联动
逐行阅读下面的代码,先在纸上写出每个 print 的输出,再运行验证。
def split_groups(members):
mid = len(members) // 2
return members[:mid], members[mid:]
squad = ["张三", "李四", "王五", "赵六", "钱七", "孙八"]
group_a, group_b = split_groups(squad)
print(group_a, group_b)
def locate_rounds(log, target):
positions = []
for i, val in enumerate(log):
if val == target:
positions.append(i)
return positions
rounds = [90, 88, 90, 75, 90]
hits = locate_rounds(rounds, 90)
print(hits)
combined = group_b + [len(hits)]
print(combined)
练习四:函数综合编程
按要求实现下面三个函数,并自行编写测试代码逐个调用,确认功能正确。
任务 1:展开嵌套清单
实现函数 unfold_layers,接收一个可能多层嵌套的列表(如 [7, [8, 9], [[10], 11]]),把所有元素摊平成一维列表返回(如 [7, 8, 9, 10, 11])。
nested1 = [7, [8, 9], [[10], 11]]
print(unfold_layers(nested1)) # [7, 8, 9, 10, 11]
nested2 = [[[7]], 8, [9, [10, [11]]]]
print(unfold_layers(nested2)) # [7, 8, 9, 10, 11]
print(unfold_layers([])) # []
print(unfold_layers([7, 8, 9])) # [7, 8, 9]
任务 2:序列变链式结构
实现函数 build_chain,接收一个列表或元组,把它转换成链式嵌套字典返回。
chain1 = build_chain((3, 6, 9))
print(chain1)
# {'value': 3, 'next': {'value': 6, 'next': {'value': 9, 'next': None}}}
chain2 = build_chain(["甲", "乙"])
print(chain2)
# {'value': '甲', 'next': {'value': '乙', 'next': None}}
print(build_chain([])) # None
任务 3:合并任意多个字典
实现函数 combine_settings,接收数量不定的字典参数,把它们合并成一个大字典。
合并规则:取并集;若键撞车,后传入的值覆盖先传入的值(浅合并即可)。
c1 = {"volume": 30, "subtitle": ["简", "英"]}
c2 = {"subtitle": ["繁"], "quality": "1080p"}
c3 = {"volume": 55, "autoplay": False}
merged = combine_settings(c1, c2, c3)
print(merged)
# {'volume': 55, 'subtitle': ['繁'], 'quality': '1080p', 'autoplay': False}
print(combine_settings(c1))
# {'volume': 30, 'subtitle': ['简', '英']}
print(combine_settings())
# {}
练习五:lambda 与高阶函数综合实战
基于下面这份超市商品数据完成练习。
goods = [
{"title": "红富士苹果", "brand": "绿洲农场", "price": 12.9, "stock": 300},
{"title": "赣南脐橙", "brand": "绿洲农场", "price": 9.9, "stock": 450},
{"title": "海南香蕉", "brand": "绿洲农场", "price": 6.5, "stock": 800},
{"title": "阳光玫瑰葡萄", "brand": "绿洲农场", "price": 29.9, "stock": 150},
{"title": "库尔勒香梨", "brand": "绿洲农场", "price": 11.5, "stock": 260},
{"title": "沙瓤西瓜", "brand": "绿洲农场", "price": 15.8, "stock": 120},
{"title": "樱桃小番茄", "brand": "绿洲农场", "price": 13.9, "stock": 240},
{"title": "盒装蓝莓", "brand": "绿洲农场", "price": 39.9, "stock": 80},
{"title": "水仙芒", "brand": "绿洲农场", "price": 14.8, "stock": 230},
{"title": "东北大米", "brand": "金穗粮油", "price": 59, "stock": 200},
{"title": "有机小米", "brand": "金穗粮油", "price": 15, "stock": 180},
{"title": "压榨花生油", "brand": "金穗粮油", "price": 89, "stock": 90},
{"title": "高筋面粉", "brand": "金穗粮油", "price": 22, "stock": 160},
{"title": "荞麦挂面", "brand": "金穗粮油", "price": 18, "stock": 210},
{"title": "即食燕麦片", "brand": "金穗粮油", "price": 21, "stock": 190},
{"title": "厄瓜多尔白虾", "brand": "蓝海水产", "price": 49.9, "stock": 85},
{"title": "鲜活鲈鱼", "brand": "蓝海水产", "price": 25, "stock": 60},
{"title": "带鱼段", "brand": "蓝海水产", "price": 19.9, "stock": 140},
{"title": "冷冻扇贝肉", "brand": "蓝海水产", "price": 35, "stock": 75},
{"title": "头水紫菜", "brand": "蓝海水产", "price": 8.5, "stock": 300},
{"title": "每日鲜牛奶", "brand": "晨光乳业", "price": 13.9, "stock": 400},
{"title": "老酸奶", "brand": "晨光乳业", "price": 8.5, "stock": 350},
{"title": "动物黄油", "brand": "晨光乳业", "price": 32, "stock": 110},
{"title": "芝士片", "brand": "晨光乳业", "price": 26, "stock": 150},
{"title": "纯羊奶", "brand": "晨光乳业", "price": 19.9, "stock": 90},
{"title": "高原水牛奶", "brand": "晨光乳业", "price": 15.9, "stock": 130},
{"title": "原味瓜子", "brand": "久久零食", "price": 11, "stock": 260},
{"title": "琥珀核桃仁", "brand": "久久零食", "price": 25, "stock": 130},
{"title": "芒果干", "brand": "久久零食", "price": 16.9, "stock": 170},
{"title": "香辣豆皮", "brand": "久久零食", "price": 5.5, "stock": 600},
{"title": "蜜汁猪肉脯", "brand": "久久零食", "price": 28, "stock": 95},
{"title": "海苔卷", "brand": "久久零食", "price": 9.9, "stock": 280},
{"title": "开口板栗仁", "brand": "久久零食", "price": 19, "stock": 145},
{"title": "天然矿泉水", "brand": "山泉饮品", "price": 24, "stock": 1000},
{"title": "白桃气泡水", "brand": "山泉饮品", "price": 45, "stock": 300},
{"title": "鲜榨橙汁", "brand": "山泉饮品", "price": 12, "stock": 220},
{"title": "冷萃咖啡液", "brand": "山泉饮品", "price": 39, "stock": 130},
{"title": "蜂蜜柚子茶", "brand": "山泉饮品", "price": 29, "stock": 160},
{"title": "椰子水", "brand": "山泉饮品", "price": 10.9, "stock": 260},
{"title": "柠檬茶", "brand": "山泉饮品", "price": 8.8, "stock": 320},
]
充分运用本章学过的 lambda 表达式与内置高阶函数,完成下面的任务:
-
按单价从低到高排序
-
按单价从高到低排序
-
按库存金额从低到高排序(库存金额 = 单价 × 库存数量)
-
找出"绿洲农场"的所有商品,得到一个字符串列表
-
找出单价最高的商品所属的品牌列表(字符串列表)
-
得到每个品牌的平均单价 结果示例:
[
{"brand": "山泉饮品", "avg_price": 24.1},
{"brand": "绿洲农场", "avg_price": 17.233333333333334},
# ……其余品牌以此类推
]
下一章:05 · 作用域规则与闭包