
1. 题目考点
这道题主要考 4 个点:
- 列表嵌套列表
每个人的信息会被读成一个小列表,例如:
["Mike", "Thomson", "20", "M"]
多人信息就是:
[
["Mike", "Thomson", "20", "M"],
["Robert", "Bustle", "32", "M"],
["Andria", "Bustle", "30", "F"]
]
- 排序
要按照年龄从小到大排序。 - 字符串格式化
男性前面加Mr.,女性前面加Ms.。 - 装饰器 decorator
题目要求你用装饰器把“排序逻辑”包在外层,把“单个人名格式化逻辑”留给原函数。
2. 审题
题目给你 N 个人的信息。每个人一行,包含:
first_name last_name age sex
例如:
Mike Thomson 20 M
含义是:
| 位置 | 含义 |
|---|---|
person[0] | 名字 first name |
person[1] | 姓氏 last name |
person[2] | 年龄 age |
person[3] | 性别 sex |
输出时要变成:
Mr. Mike Thomson
或者:
Ms. Andria Bustle
排序规则是:
- 按年龄从小到大排序;
- 如果两个人年龄一样,保持原来的输入顺序。
Python 的 sorted() 本身是稳定排序,所以年龄相同的人会自动保持原输入顺序。
3. 思路提示
先不要急着写代码。你可以把这道题拆成两个任务。
第一个任务:怎么把一个人格式化成名字?
例如:
["Mike", "Thomson", "20", "M"]
如果性别是 M,输出:
Mr. Mike Thomson
如果性别是 F,输出:
Ms. Mary George
第二个任务:怎么先按年龄排序,再逐个格式化?
也就是先把:
[
["Mike", "Thomson", "20", "M"],
["Robert", "Bustle", "32", "M"],
["Andria", "Bustle", "30", "F"]
]
按照 person[2] 年龄排序。
注意:person[2] 是字符串,比如 "20",所以排序时最好转成整数:
int(person[2])
4. 完整设计思路
题目通常会给你这样的模板:
def person_lister(f):
def inner(people):
# complete the function
return inner
@person_lister
def name_format(person):
return ("Mr. " if person[3] == "M" else "Ms. ") + person[0] + " " + person[1]
这里最关键的是理解这句:
@person_lister
它的意思相当于:
name_format = person_lister(name_format)
也就是说,原来的 name_format 函数会被传进 person_lister(f) 里面,变成参数 f。
所以:
f(person)
其实就是在调用原来的:
name_format(person)
因此 inner(people) 里面应该做三件事:
第一步,接收所有人的信息:
people
第二步,按照年龄排序:
sorted_people = sorted(people, key=lambda person: int(person[2]))
第三步,对排序后的每个人调用 f(person),得到格式化结果:
result = []
for person in sorted_people:
result.append(f(person))
最后返回 result。
5. 代码实现
完整代码如下:
def person_lister(f):
def inner(people):
sorted_people = sorted(people, key=lambda person: int(person[2]))
result = []
for person in sorted_people:
result.append(f(person))
return result
return inner
@person_lister
def name_format(person):
if person[3] == "M":
title = "Mr. "
else:
title = "Ms. "
return title + person[0] + " " + person[1]
if __name__ == '__main__':
people = [input().split() for i in range(int(input()))]
print(*name_format(people), sep='\n')
也可以把 inner 写得更短一些:
def person_lister(f):
def inner(people):
sorted_people = sorted(people, key=lambda person: int(person[2]))
return [f(person) for person in sorted_people]
return inner
对于初学阶段,前一种写法更直观。
6. 模板代码解释
这一行:
people = [input().split() for i in range(int(input()))]
可以拆开理解。
假设输入是:
3
Mike Thomson 20 M
Robert Bustle 32 M
Andria Bustle 30 F
第一行:
int(input())
读入人数 3。
然后循环 3 次,每次读一行:
input().split()
例如:
"Mike Thomson 20 M".split()
会得到:
["Mike", "Thomson", "20", "M"]
最终 people 就是:
[
["Mike", "Thomson", "20", "M"],
["Robert", "Bustle", "32", "M"],
["Andria", "Bustle", "30", "F"]
]
最后这一句:
print(*name_format(people), sep='\n')
意思是:
- 调用
name_format(people); - 由于
name_format被装饰器改造过,所以实际调用的是inner(people); inner(people)返回一个格式化后的名字列表;print(*列表, sep='\n')把列表中的每个元素单独打印,并且每个元素之间换行。
7. 运行演示
输入:
3
Mike Thomson 20 M
Robert Bustle 32 M
Andria Bustle 30 F
读入后:
people = [
["Mike", "Thomson", "20", "M"],
["Robert", "Bustle", "32", "M"],
["Andria", "Bustle", "30", "F"]
]
按照年龄排序:
[
["Mike", "Thomson", "20", "M"],
["Andria", "Bustle", "30", "F"],
["Robert", "Bustle", "32", "M"]
]
然后逐个格式化。
第一个人:
["Mike", "Thomson", "20", "M"]
性别是 M,输出:
Mr. Mike Thomson
第二个人:
["Andria", "Bustle", "30", "F"]
性别是 F,输出:
Ms. Andria Bustle
第三个人:
["Robert", "Bustle", "32", "M"]
性别是 M,输出:
Mr. Robert Bustle
最终输出:
Mr. Mike Thomson
Ms. Andria Bustle
Mr. Robert Bustle
8. 这道题最关键的地方
这道题不是让你在 name_format 里面排序。
name_format(person) 只负责处理一个人:
["Mike", "Thomson", "20", "M"]
它不负责处理所有人。
真正处理所有人的地方是装饰器里面的 inner(people):
def inner(people):
sorted_people = sorted(people, key=lambda person: int(person[2]))
return [f(person) for person in sorted_people]
你可以这样记:
原函数:处理一个人
装饰器:处理一群人
9. 方法总结
以后看到这类装饰器题,可以按这个套路思考:
第一,看原函数要处理什么。
这里原函数:
name_format(person)
处理的是“一个人”。
第二,看装饰器要额外增加什么功能。
这里装饰器:
person_lister(f)
要增加的是“对一群人排序,然后逐个格式化”。
第三,在装饰器内部调用原函数。
也就是:
f(person)
这一步表示:排序完成后,再把每个人交给原函数处理。
10. 同类型练习
给你若干学生信息:
name score
要求按照分数从低到高排序,然后输出:
Student: name
例如输入:
3
Tom 80
Jack 60
Mary 90
输出:
Student: Jack
Student: Tom
Student: Mary
提示:
你可以写一个原函数专门格式化一个学生:
def student_format(student):
return "Student: " + student[0]
然后写一个装饰器,在装饰器里面根据 student[1] 排序。
补充说明
那按理来说,装饰器函数是一个这么样一个的模板
def my_decorator(func):
def wrapper(*args, **kwargs):
# 原函数执行前
result = func(*args, **kwargs)
# 原函数执行后
return result
return wrapper
那为什么我并没有看到这一条 result = func(*args, **kwargs),就是说明确的result,所以这个装饰器里面这个分割到底是哪一条?
你的理解方向是对的,但有一个关键点要修正:
你看到的这个模板:
def my_decorator(func):
def wrapper(*args, **kwargs):
# 原函数执行前
result = func(*args, **kwargs)
# 原函数执行后
return result
return wrapper
它只是最常见的一种装饰器写法,不是所有装饰器都必须长这样。
这道题里的装饰器不是简单地“在原函数前后加点东西”,而是:
原函数 name_format:只会处理一个人
装饰器 person_lister:让它变成能处理一群人
所以这道题的核心变化是:装饰器改变了函数的使用方式。
1. 普通装饰器模板里,哪一句是调用原函数?
在这个模板里:
result = func(*args, **kwargs)
这一句就是在调用原函数。
其中:
func
就是被装饰的原函数。
比如:
@my_decorator
def hello():
print("hello")
等价于:
hello = my_decorator(hello)
所以 func 指向原来的 hello。
2. 这道题里面,哪一句对应 func(*args, **kwargs)?
这道题常见代码是:
def person_lister(f):
def inner(people):
sorted_people = sorted(people, key=lambda person: int(person[2]))
return [f(person) for person in sorted_people]
return inner
这里的:
f(person)
就对应普通模板里的:
func(*args, **kwargs)
也就是说:
f(person)
就是在调用原来的 name_format(person)。
因为外面有:
@person_lister
def name_format(person):
return ("Mr. " if person[3] == "M" else "Ms. ") + person[0] + " " + person[1]
所以这里的 f 实际上就是原来的 name_format 函数。
你可以把它理解成:
return [name_format(person) for person in sorted_people]
只是因为用了装饰器,所以在 person_lister(f) 里面要写成:
f(person)
3. 为什么没有看到明确的 result = ...?
因为 result 这个变量不是必须的。
这两种写法本质一样:
result = func(x)
return result
和:
return func(x)
是等价的。
比如普通装饰器可以写成:
def my_decorator(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
不一定非要写:
result = func(*args, **kwargs)
return result
所以这道题里面:
return [f(person) for person in sorted_people]
其实相当于:
result = []
for person in sorted_people:
result.append(f(person))
return result
再展开一点,就是:
result = []
for person in sorted_people:
formatted_name = f(person)
result.append(formatted_name)
return result
这里的:
formatted_name = f(person)
才是最接近:
result = func(*args, **kwargs)
的地方。
4. 为什么这里不能直接写 result = f(*args, **kwargs)?
这是这道题最容易卡住的地方。
原函数是:
def name_format(person):
...
它只会处理一个人,比如:
["Mike", "Thomson", "20", "M"]
但是装饰之后,我们实际调用的是:
name_format(people)
这里传进去的是一群人:
[
["Mike", "Thomson", "20", "M"],
["Robert", "Bustle", "32", "M"],
["Andria", "Bustle", "30", "F"]
]
如果直接写:
result = f(people)
那就等于把“所有人列表”直接丢给原函数。
可是原函数 name_format(person) 期待的是:
["Mike", "Thomson", "20", "M"]
不是:
[
["Mike", "Thomson", "20", "M"],
["Robert", "Bustle", "32", "M"],
["Andria", "Bustle", "30", "F"]
]
所以这道题必须先遍历:
for person in sorted_people:
f(person)
意思是:把一群人拆成一个一个人,再交给原函数处理。
5. 这道题装饰器里的“分割线”到底在哪里?
你可以这样看:
def person_lister(f): # f 接收原来的 name_format 函数
def inner(people): # inner 是新的 name_format 函数
sorted_people = sorted(
people,
key=lambda person: int(person[2])
) # 原函数执行前:先排序
return [f(person) for person in sorted_people]
# 调用原函数:f(person)
# 原函数执行后:把结果组成列表返回
return inner # 用 inner 替换原来的 name_format
真正的分界点是:
f(person)
它就是“调用原函数”的地方。
更清楚地写成普通循环就是:
def person_lister(f):
def inner(people):
sorted_people = sorted(people, key=lambda person: int(person[2]))
result = []
for person in sorted_people:
formatted_name = f(person)
result.append(formatted_name)
return result
return inner
在这段代码里,结构就非常明显了:
sorted_people = ...
这是原函数执行前新增的功能:排序。
formatted_name = f(person)
这是调用原函数:格式化一个人的名字。
result.append(formatted_name)
return result
这是原函数执行后的处理:收集所有格式化结果并返回。
6. 用一句话总结
普通装饰器模板里:
result = func(*args, **kwargs)
表示“调用原函数”。
这道题里对应的是:
f(person)
只是这道题不是调用一次原函数,而是:
先把所有人按年龄排序
再把每个人分别交给原函数格式化
最后把所有结果组成列表返回
所以它不写成:
result = func(*args, **kwargs)
而是写成:
return [f(person) for person in sorted_people]
7. 小练习
把下面这段列表推导式改写成普通 for 循环:
return [f(person) for person in sorted_people]
提示:你需要先创建一个空列表 result = [],然后遍历 sorted_people,每次调用 f(person),再把结果加入 result。
本质上就是一个调用函数,装饰器函数里面掉用了一个被装饰的函数,那为什么不用组合或者继承呢?
对,可以这样理解,但要再精确一点:
装饰器的本质不是“调用函数”,而是“接收一个函数,返回一个新函数”。
这个“新函数”里面通常会调用原来的被装饰函数,但不是必须调用,也可以调用多次,甚至可以完全不调用。
1. 更准确的理解
普通函数调用是这样:
result = func()
而装饰器是这样:
def my_decorator(func):
def wrapper():
result = func()
return result
return wrapper
这里有两层关系:
第一层,装饰器函数接收原函数:
def my_decorator(func):
第二层,内部新函数调用原函数:
result = func()
所以装饰器完整理解应该是:
装饰器函数接收一个函数作为参数,
然后返回一个新的函数,
这个新的函数可以在内部调用原函数。
这比“装饰器里面调用了被装饰函数”更准确。
2. @decorator 到底发生了什么?
比如:
@my_decorator
def hello():
print("hello")
它等价于:
def hello():
print("hello")
hello = my_decorator(hello)
注意这里非常关键:
hello = my_decorator(hello)
这说明原来的 hello 被传进了 my_decorator,然后 my_decorator 返回了一个新的函数,重新赋值给 hello。
所以之后你再调用:
hello()
你调用的已经不是原来的 hello,而是装饰器返回的 wrapper。
3. 套回这道题
这道题里面:
def person_lister(f):
def inner(people):
sorted_people = sorted(people, key=lambda person: int(person[2]))
return [f(person) for person in sorted_people]
return inner
然后:
@person_lister
def name_format(person):
...
等价于:
name_format = person_lister(name_format)
也就是说,装饰之后:
name_format
已经不再直接指向原来的单人格式化函数,而是指向了 inner。
所以当你写:
name_format(people)
实际执行的是:
inner(people)
而 inner(people) 内部又会调用:
f(person)
这里的 f 才是原来的 name_format(person)。
这道题的关系可以写成:
原来的 name_format(person)
功能:格式化一个人
装饰器返回的 inner(people)
功能:接收一群人,排序,然后一个一个交给原来的 name_format
4. 那为什么不用组合?
实际上,装饰器本身就是一种函数层面的组合。
不用装饰器的话,我们完全可以这样写:
def name_format(person):
if person[3] == "M":
title = "Mr. "
else:
title = "Ms. "
return title + person[0] + " " + person[1]
def sorted_name_format(people):
sorted_people = sorted(people, key=lambda person: int(person[2]))
result = []
for person in sorted_people:
result.append(name_format(person))
return result
这就是普通组合:
sorted_name_format 调用了 name_format
这当然可以,而且对初学者来说更直观。
装饰器只是把这种组合写成了:
@person_lister
def name_format(person):
...
它的好处是:不改原函数本身,只在外面包一层功能。
5. 装饰器和普通组合的区别
普通组合通常是显式调用另一个函数:
def sorted_name_format(people):
return [name_format(person) for person in people]
你能直接看到:
name_format(person)
所以普通组合更直观。
装饰器是把“包装逻辑”提前绑定到函数名上:
@person_lister
def name_format(person):
...
之后你调用:
name_format(people)
表面上像是在调用 name_format,实际上已经调用了装饰器返回的新函数。
所以装饰器的优点是简洁,缺点是对初学者来说不够直观。
这也是为什么你会觉得疑惑:明明看起来在调用 name_format,但实际跑的是 inner。
6. 那为什么不用继承?
因为继承主要用于“类和类之间”的关系,而这道题处理的是“函数和函数之间”的包装关系。
继承适合这种场景:
class Animal:
def speak(self):
pass
class Dog(Animal):
def speak(self):
print("wang")
它表达的是:
Dog 是一种 Animal
也就是所谓的 “is-a” 关系。
但是这道题不是:
name_format 是一种 person_lister
这个说法不成立。
这道题更像是:
给 name_format 外面套一层排序功能
所以继承不适合。
7. 什么时候适合用装饰器?
装饰器适合给函数统一加一层“额外功能”,尤其是这些功能不属于函数本身的核心业务。
比如:
统计运行时间
打印日志
权限检查
缓存结果
参数检查
异常处理
重试机制
格式转换
举个简单例子:
def log_decorator(func):
def wrapper():
print("函数开始执行")
result = func()
print("函数执行结束")
return result
return wrapper
@log_decorator
def say_hello():
print("hello")
say_hello()
执行结果:
函数开始执行
hello
函数执行结束
这里 say_hello 的核心功能只是打印 hello,而“函数开始执行”“函数执行结束”属于额外功能,所以适合用装饰器。
8. 什么时候不适合用装饰器?
如果逻辑本身很重要,或者装饰器会让函数的输入输出变得不明显,就不一定适合用装饰器。
比如这道题从工程角度看,其实普通写法更清楚:
def name_format(person):
...
def sorted_name_format(people):
...
但是题目要求用装饰器,是为了训练你理解:
函数可以作为参数传递
函数可以作为返回值返回
装饰器可以改变函数的行为
所以这题有一定“教学题”的味道,不一定代表真实项目里非要这么写。
9. 最重要的一句话
你可以这样总结:
装饰器 = 用一个函数包装另一个函数。
更完整地说:
装饰器函数接收原函数,返回一个新函数;
新函数通常会在内部调用原函数,并在调用前后增加额外逻辑。
在这道题里:
原函数 name_format:负责格式化一个人
装饰器 person_lister:负责接收一群人、排序、逐个调用原函数
也就是:
装饰器负责“整体流程”
原函数负责“单个元素怎么处理”
10. 小练习
你可以判断下面这个装饰器中,哪一句是在“调用原函数”?
def repeat_twice(func):
def wrapper():
func()
func()
return wrapper
@repeat_twice
def say_hi():
print("hi")
say_hi()
提示:这段代码中,原函数不是被调用一次,而是被调用了两次。



