函数
随着程序规模的增长,你会发现同一段逻辑要在多处重复编写。函数让你把逻辑写一次、起个名字,然后随处调用。修改的时候只需要改一处,每个调用点都会自动获得修复。
def greet(name):
return f"Hello, {name}!"
print(greet("小明")) # "Hello, 小明!"
print(greet("小红")) # "Hello, 小红!"写一次,到处用,改一处就够了。
定义函数
def 关键字用来开始一个函数定义,后面跟着函数名、括号、冒号和缩进的函数体。函数在被调用之前什么都不会做。用 def 定义它,再用 () 按名字调用它。
def say_hello():
print("Hello!")
say_hello() # 调用这个函数def name():,然后把函数体缩进写在下面。在你用 name() 调用它之前什么都不会发生,def 只是把它准备好而已。如果你忘了写 return,函数会默默地返回 None,我早期就因为这个栽过不止一次。 参数与实参
参数是你的函数所期望的输入。把它们列在括号里。调用函数时,你传入的值会按顺序对应到这些参数上。
def greet(name, greeting):
print(f"{greeting}, {name}!")
greet("小明", "Hello") # "Hello, 小明!"
greet("小红", "Hi") # "Hi, 小红!"参数(parameter)是函数定义中的名字。实参(argument)是你调用时实际传入的值。实际使用中大家常常把这两个词混用;这个区分主要在读文档时才用得上。
默认值
你可以给参数设置默认值。如果调用方没有提供这个实参,就会使用默认值。带默认值的参数必须写在不带默认值的参数后面。
def greet(name, greeting="Hello"):
print(f"{greeting}, {name}!")
greet("小明") # "Hello, 小明!"
greet("小明", "Hi") # "Hi, 小明!"带默认值的参数必须写在不带默认值的参数后面。
关键字实参
调用函数时,你可以给实参命名。这会让调用更容易读懂,尤其是对参数很多的函数,而且还能让你以任意顺序传值。
def describe_player(name, score, level):
print(f"{name} | Score: {score} | Level: {level}")
describe_player("小明", 87, 5) # 位置传参
describe_player(name="小明", level=5, score=87) # 关键字传参,顺序随意
describe_player("小明", level=5, score=87) # 混合:位置在前score=87),调用本身就能说明自己在做什么,而且顺序也随意了。唯一的规则是:位置实参必须写在命名实参前面。对参数一大堆的函数特别好用。 返回值
return 把一个值送回给调用方。没有 return,函数会返回 None。一旦 return 执行,函数就会立刻退出。它之后的同一代码块里的其他代码都会被跳过。
def add(a, b):
return a + b
result = add(3, 4) # result = 7
print(result)return 也会立刻退出函数。它之后同一代码块里的代码不会执行。
return 把一个值送回给调用这个函数的地方,函数就此停下,之后的代码都会被跳过。不写 return 就会得到 None。print 是把值显示出来,return 是把值交回去让你继续用,这个区别我花了点时间才真正体会到。 返回多个值
Python 允许你用逗号分隔的方式返回多个值。调用方会以元组的形式接收它们,并可以在一行代码里把它们拆成不同的名字。
def min_max(numbers):
return min(numbers), max(numbers)
low, high = min_max([3, 7, 1, 9, 4])
print(low, high) # 1 9low, high = ... 这种写法叫解包(unpacking):Python 把每个返回值分别赋给对应的名字。
low, high = min_max(...),一个值对应一个名字。只要函数天然会产生多个结果,这种写法就很好用。 作用域
在函数内部创建的变量只存在于这个函数内部。你从外部是看不到它们的。在所有函数之外定义的变量在任何地方都可见,但你不能不经过显式声明就在函数内部修改它们。
global。这种情况几乎用不上:把值传进去,把结果返回出来,函数就始终保持简单易懂。 def calculate():
result = 42 # 局部于这个函数
return result
calculate()
print(result) # NameError,result 在这里并不存在count = 0
def increment():
global count # 声明你要修改这个全局变量
count += 1
increment()
print(count) # 1使用 global 应该是最后的手段。它会让代码更难推理。优先把值传进去,再把结果返回出来。作用域直接建立在赋值如何把名字绑定到值这一机制之上,这部分内容在变量与类型一章有详细介绍。
*args 和 **kwargs
有时候你无法提前知道一个函数会接收多少个实参。*args 会把任意数量的位置实参收集到一个元组里。**kwargs 会把任意数量的关键字实参收集到一个字典里。args 和 kwargs 这两个名字只是约定俗成,真正起作用的是星号。
def total(*args):
return sum(args)
total(1, 2, 3) # 6
total(1, 2, 3, 4, 5) # 15def display(**kwargs):
for key, value in kwargs.items():
print(f"{key}: {value}")
display(name="小明", score=87, level=5)你可以把它们和普通参数混用。普通参数必须写在前面:
def describe(title, *tags, **metadata):
print(f"{title} | tags: {tags} | meta: {metadata}")
describe("Python 入门", "beginner", "python", author="小明", year=2024)*args 会把任意数量的多余位置实参收集成一个元组,**kwargs 会把多余的命名实参收集成一个字典。args 和 kwargs 这两个词只是约定,真正起作用的是 * 和 **。当你事先说不清会传进来多少个东西时,这就很好用。 文档字符串(Docstrings)
文档字符串是函数顶部的一段字符串,用来描述这个函数做什么。Python 的编辑器和工具会用它在你把鼠标悬停在函数调用上时显示帮助信息。用三重引号书写,简单的函数写一行就够了。
def normalise(value, min_val, max_val):
"""在已知最小值和最大值的情况下,把一个值缩放到 0-1 的范围。"""
return (value - min_val) / (max_val - min_val)def build_url(base, version, resource, *, secure=True):
"""
构建一个 API 端点 URL。
返回一个完整的 URL 字符串。如果 secure 为 False,
URL 会使用 http 而不是 https。
"""
scheme = "https" if secure else "http"
base = base.replace("https://", "").replace("http://", "")
return f"{scheme}://{base}/{version}/{resource}"只要一个函数的用途从名字和签名上看不出来,就该给它写文档字符串。
类型提示
类型提示让你能够标注一个函数期望接收什么类型、返回什么类型。Python 在运行时并不会强制检查它们,但编辑器会利用它们在你运行代码之前就发现错误。冒号前的 -> 用来指定返回类型。
def greet(name: str, score: int) -> str:
return f"{name} scored {score}"def log(message: str) -> None:
print(f"[LOG] {message}")def top_scores(scores: list[int], n: int) -> list[int]:
return sorted(scores, reverse=True)[:n]类型提示是可选的,但对于会被多处调用的函数来说非常有价值。它们是工具可以验证的文档。
name: str,返回类型用 -> str。Python 运行时不会强制检查它们,但你的编辑器会读取它们,在你运行代码之前就提醒你。可以把它们当作工具能够核对的注释。 把函数当作值
Python 中的函数是值,就像字符串或数字一样。你可以把它们赋值给变量,也可以传给其他函数。这就是 sorted() 能接受一个 key= 函数的原理。
def double(x):
return x * 2
def apply(func, value):
return func(value)
apply(double, 5) # 10把函数当作参数传递,在 sorted()、map() 和 filter() 里随处可见。你在lambda 与推导式一章也会见到它。
key= 传给 sorted() 的原因。我一开始觉得这挺奇怪的,后来一下就想通了,而且到处都能用上。 实践演练
两个配合使用的函数:letter_grade 把分数转换成字母等级,summarise 对列表里的每个分数调用它:
def letter_grade(score: int) -> str:
if score >= 90:
return "A"
elif score >= 80:
return "B"
elif score >= 70:
return "C"
else:
return "F"
def summarise(scores: list[int]) -> None:
total = sum(scores)
avg = total / len(scores)
grades = []
for s in scores:
grades.append(letter_grade(s))
print(f"Average: {avg:.1f}")
print(f"Grades: {', '.join(grades)}")
summarise([87, 92, 74, 65, 91])
