列表嵌套列表+装饰器 decorator

1. 题目考点

这道题主要考 4 个点:

  1. 列表嵌套列表
    每个人的信息会被读成一个小列表,例如:
["Mike", "Thomson", "20", "M"]

多人信息就是:

[
    ["Mike", "Thomson", "20", "M"],
    ["Robert", "Bustle", "32", "M"],
    ["Andria", "Bustle", "30", "F"]
]
  1. 排序
    要按照年龄从小到大排序。
  2. 字符串格式化
    男性前面加 Mr.,女性前面加 Ms.
  3. 装饰器 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

排序规则是:

  1. 按年龄从小到大排序;
  2. 如果两个人年龄一样,保持原来的输入顺序。

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')

意思是:

  1. 调用 name_format(people)
  2. 由于 name_format 被装饰器改造过,所以实际调用的是 inner(people)
  3. inner(people) 返回一个格式化后的名字列表;
  4. 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()

提示:这段代码中,原函数不是被调用一次,而是被调用了两次。

文末附加内容
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇