为什么要定义函数(Function)?
当程序规模变大时,如果一段重复的计算逻辑在代码各处复制粘贴,一旦需要修改就会引发灾难性的维护成本。**函数(function)**是编程中最重要的抽象工具:它将一段具有明确输入与输出的独立逻辑封装在一起,起一个名字,供随时调用。
1. 使用 def 关键字定义函数
在 Python 中,使用关键字 def(define 的缩写)来创建函数:
def fib(n):
"""Print a Fibonacci series less than n."""
a, b = 0, 1
while a < n:
print(a, end=' ')
a, b = b, a + b
print()
# 调用我们定义的函数:
fib(2000)
输出:
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597
核心解构:
- 函数签名:
def fib(n):,以def开头,后跟函数名和圆括号内的形参列表(parameters),行末紧跟冒号; - 函数体缩进:冒号下方属于函数内部的所有语句必须统一向右缩进 4 个空格;
- 文档字符串(docstring):函数内部的第一条语句如果是一个多行字符串
"""...""",它会被自动识别为文档字符串。它是对该函数功能、参数与返回值的权威说明,可以通过help(fib)或开发工具直接查阅,编写清晰的 docstring 是专业开发者的标志性习惯; - 函数也是第一类对象:函数名
fib实际上指向了一个可调用的函数对象。你可以把它赋值给其他变量(如f = fib),然后使用f(100)来调用它。
2. 关键心智模型:print() 与 return 的根本差异
初学者经常混淆“屏幕打印”与“函数返回”:
print():纯粹是向屏幕输出字符给人眼阅读。外层的调用代码根本“拿不到”这个值;return:是把计算生成的数据“交还”给调用方。外部可以用变量承接这个结果,并进行后续存储或二次计算;- 没有 return 的函数:如果函数执行完毕没有遇到任何
return语句,Python 会默认默默返回内置特殊对象None(表示“空”)。
官方教程通过对比 fib(n) 与 fib2(n) 深刻阐明了这一点:
def fib2(n):
"""Return a list containing the Fibonacci series up to n."""
result = []
a, b = 0, 1
while a < n:
result.append(a)
a, b = b, a + b
return result
# 调用它并用变量接收返回值:
f100 = fib2(100)
print(f100)
输出:
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
此时,f100 是一个真正的列表对象,你可以对它求 len(f100) 或进行切片筛选。
3. 函数参数详解
Python 拥有极其灵活多样的传参机制:
默认值参数(Default Argument Values)
可以为形参指定默认值,调用时如果省略该参数,则自动采用默认值:
def ask_ok(prompt, retries=4, reminder='Please try again!'):
while True:
reply = input(prompt)
if reply in {'y', 'ye', 'yes'}:
return True
if reply in {'n', 'no', 'nop', 'nope'}:
return False
retries = retries - 1
if retries < 0:
raise ValueError('invalid user response')
print(reminder)
重要陷阱:可变默认参数只在函数定义时求值一次! 如果默认值是一个可变对象(如列表
L=[]),后续多次调用会共享并累积同一个列表:def f(a, L=[]): L.append(a) return L print(f(1)) # [1] print(f(2)) # [1, 2] -- 发生了累积!官方推荐的安全写法:默认值设为
None,并在函数体内动态创建新列表:def f(a, L=None): if L is None: L = [] L.append(a) return L
关键字参数(Keyword Arguments)
调用函数时,可以通过 形参名=值 的方式明确指定实参,无需死记位置顺序:
def parrot(voltage, state='a stiff', action='voom', type='Norwegian Blue'):
print("-- This parrot wouldn't", action, end=' ')
print("if you put", voltage, "volts through it.")
# 合法调用方式:
parrot(1000) # 1 个位置参数
parrot(voltage=1000) # 1 个关键字参数
parrot(voltage=1000000, action='VOOOOOM') # 2 个关键字参数
parrot('a thousand', state='pushing up daisies') # 位置参数必须在关键字参数之前
任意实参列表:*args 与 **kwargs
*args:将多余传入的位置参数打包为一个元组(tuple);**kwargs:将多余传入的关键字参数打包为一个字典(dict):
def cheeseshop(kind, *arguments, **keywords):
print("-- Do you have any", kind, "?")
for arg in arguments:
print(arg)
for kw in keywords:
print(kw, ":", keywords[kw])
解包实参列表(Unpacking Argument Lists)
如果你手头已经有一个列表或元组,想把它解开当成独立的位置参数传给函数,可以在前面加一个星号 *;如果是一个字典,可以使用两个星号 ** 解包为关键字参数:
args = [3, 6]
list(range(*args)) # 等价于 range(3, 6)
Lambda 表达式
可以使用 lambda 关键字创建小巧的单行匿名函数,常用于排序键值:
pairs = [(1, 'one'), (2, 'two'), (3, 'three'), (4, 'four')]
pairs.sort(key=lambda pair: pair[1])
掌握了函数及其传参体系,我们已经能够将代码组织得井井有条。下一关,我们将深入 Python 丰富的数据结构,掌握堆栈、队列、推导式与字典的高效用法!