Skip to content
This page has been auto-translated and may contain errors.View in English

修虫小屋

恭喜你。你成为了 Crooked Orbit 的技术总监,这是一家小型、略显混乱的太空物流公司,由一群可疑的宇航员经营,他们的代码更加可疑。

薪水还不错。代码库就……

你的工作是处理这些问题:bug、坏掉的函数,以及那些技术上能运行但其实不该运行的东西。

#1:姓名格式化器

Pip 嘿,我写了这个姓名格式化器,但它就是……什么都不做?😭 它运行没报错,但名字还是全小写的。我在循环里对每个名字调用了 .capitalize(),我能在循环里看到它。我搞不懂。能麻烦你看一下吗?
python
def format_name(full_name):
    parts = full_name.split()
    for part in parts:
        part = part.capitalize()
    return " ".join(parts)

print(format_name("alice van den berg"))

期望: Alice Van Den Berg

实际: alice van den berg

哪里出错了?该怎么修复?

提示

想想在 for 循环中重新赋值变量时究竟发生了什么。改变 part 会影响它所在的列表吗?

一种修复方法

当你写 for part in parts 时,Python 一次从列表中取一项给你。part 只是一个临时变量,持有当前的项。它和列表本身没有直接联系。

所以当你写 part = part.capitalize() 时,你只是替换了 part 所指向的内容。但 parts 仍然保存着原来的字符串。你从未真正改变过这个列表。

这里有一个清楚的演示:

python
parts = ["alice", "van", "den", "berg"]

for part in parts:
    part = part.capitalize()

print(parts)
# ['alice', 'van', 'den', 'berg']  # 什么都没变

要修复它,你需要构建一个新列表,里面装着首字母大写后的值。列表推导式可以干净利落地做到这一点:

python
def format_name(full_name):
    parts = full_name.split()
    return " ".join(part.capitalize() for part in parts)

print(format_name("alice van den berg"))
# Alice Van Den Berg

part.capitalize() for part in parts 会遍历每个名字,把它首字母大写,然后把结果收集到一个新列表中。然后 .join() 用空格把它们重新拼接起来。

#2:最高分

Zee 好吧这有点尴尬,我有这个函数,本来应该找出列表中的最高分,但它就是……打印 None?每次都这样。我把循环过了大概五遍,逻辑看起来没问题。分数明明就在那里。我不知道发生了什么 😅
python
def highest_score(scores):
    best = 0
    for score in scores:
        if score > best:
            best = score

results = [45, 92, 78, 88, 65]
print(f"Top score: {highest_score(results)}")

期望: Top score: 92

实际: Top score: None

哪里出错了?该怎么修复?

提示

如果你从未写过 return 语句,函数会返回什么?

一种修复方法

循环没问题。逻辑没问题。问题在于 highest_score 从未真正返回任何东西。它找到了最高分,然后就什么也没做。

在 Python 中,如果函数没有 return 语句,它会自动返回 None。这就是你打印出来的东西。

修复方法就是在最后加一行:

python
def highest_score(scores):
    best = 0
    for score in scores:
        if score > best:
            best = score
    return best

results = [45, 92, 78, 88, 65]
print(f"Top score: {highest_score(results)}")
# Top score: 92

这种情况很常见。函数看起来已经完整,因为逻辑都在那里,但没有 return,函数结束时结果就消失了。

#3:预算检查

Orla 好吧,我在做一个小的预算追踪器,每次我输入一个数字它就崩溃。它甚至什么都不做,直接就抛出 TypeError。我检查了逻辑看起来没问题?阈值是浮点数,输入也是数字……它到底在抱怨什么……请救救我
python
def check_budget(threshold):
    spent = input("How much have you spent? ")
    if spent >= threshold:
        print("Over budget!")
    else:
        print("You're within budget.")

check_budget(500.0)

错误:

TypeError: '>=' not supported between instances of 'str' and 'float'

哪里出错了?该怎么修复?

提示

不管用户输入了什么,input() 总是返回什么类型?

一种修复方法

input() 总是返回一个字符串。用户输入了数字并不重要。Python 并不知道这一点。它只是把输入的内容当作文本交回来。

所以当代码尝试比较 spent >= threshold 时,它在比较一个字符串和一个浮点数,Python 拒绝这样做。这就是 TypeError。

修复方法是在比较之前把输入转换为数字。用 float() 来处理小数值:

python
def check_budget(threshold):
    spent = float(input("How much have you spent? "))
    if spent >= threshold:
        print("Over budget!")
    else:
        print("You're within budget.")

check_budget(500.0)
# How much have you spent? 620
# Over budget!

input() 包在 float() 里,可以立即将字符串转换为数字,这样比较就能按预期工作。

这是 Python 中最常见的困惑来源之一:input() 看起来像是在读取一个数字,但它总是给你一个字符串。你必须自己转换它。

#4:货物标签

Dex 我在为舱单生成货物标签,它一直在这一行崩溃。物品名称是字符串,重量是数字,看起来没问题啊?但 Python 在拼接这件事上发脾气 😩
python
def cargo_label(item, weight):
    label = "Item: " + item + " | Weight: " + weight + "kg"
    return label

print(cargo_label("Moon rocks", 42))

错误:

TypeError: can only concatenate str (not "int") to str

哪里出错了?该怎么修复?

提示

Python 的 + 运算符不会自动把数字转换为字符串。在连接它们之前你需要做什么?

一种修复方法

当你在 Python 中用 + 连接字符串时,每个值都必须是字符串。weight 是一个整数,所以 Python 不知道该怎么把它附加到周围的文本上。它选择拒绝而不是去猜。

修复方法是用 str()weight 转换为字符串:

python
def cargo_label(item, weight):
    label = "Item: " + item + " | Weight: " + str(weight) + "kg"
    return label

print(cargo_label("Moon rocks", 42))
# Item: Moon rocks | Weight: 42kg

对于这种情况,f-string 通常更简洁,因为它会自动处理转换:

python
def cargo_label(item, weight):
    return f"Item: {item} | Weight: {weight}kg"

print(cargo_label("Moon rocks", 42))
# Item: Moon rocks | Weight: 42kg

{} 里,Python 会自动把值转换为它的字符串表示,所以根本不需要 str()

#5:乘客名单

Cass 有件很奇怪的事情。我有这个函数,为每次航班构建一个乘客列表,第一次航班看起来没问题。但到了第三次,名单里包含了之前所有航班的人?我没有在调用之间共享任何数据,每次调用都是完全独立的。我真的不知道发生了什么 😰
python
def build_manifest(name, passengers=[]):
    passengers.append(name)
    return passengers

flight_1 = build_manifest("小明")
flight_2 = build_manifest("小红")
flight_3 = build_manifest("小华")

print(flight_1)
print(flight_2)
print(flight_3)

期望:

['小明']
['小红']
['小华']

实际:

['小明']
['小明', '小红']
['小明', '小红', '小华']

哪里出错了?该怎么修复?

提示

Python 中默认参数的值只在函数定义时求值一次。而不是每次调用函数时。对于像列表这样的可变对象来说,这意味着什么?

一种修复方法

这是 Python 最著名的意外之一。当你写 passengers=[] 作为默认参数时,Python 在函数定义时创建那个列表一次。每次使用默认值的调用都共享同一个列表对象。所以在一次调用中向它追加内容会影响所有未来的调用。

标准的修复方法是用 None 作为默认值,然后在函数内部创建一个全新的列表:

python
def build_manifest(name, passengers=None):
    if passengers is None:
        passengers = []
    passengers.append(name)
    return passengers

flight_1 = build_manifest("小明")
flight_2 = build_manifest("小红")
flight_3 = build_manifest("小华")

print(flight_1)  # ['小明']
print(flight_2)  # ['小红']
print(flight_3)  # ['小华']

现在每次不传入列表的调用都会得到自己全新的列表。

这适用于任何可变默认值:列表、字典、集合。如果你希望每次都得到一个新的,就不要把它放在函数签名里。用 None,然后在函数内部创建。