函数是给一段逻辑起名字。你一旦学会"把代码装进函数",就再也不想写一大坨平铺直叙的脚本了。
前面几篇的代码越长越长,是时候把它们收拾整理了。函数就是那个收纳盒:给一段逻辑起个名字,下次直接用名字调用。
你将学到
- 定义与调用函数,参数与返回值
- 位置参数、默认参数、关键字参数的区别与用法
*args/**kwargs收集任意参数- 作用域:局部、全局、
global,以及 LEGB 查找顺序 - 文档字符串(docstring)
- "把代码装进函数"的思维方式
- 递归入门(阶乘)
前置知识
先读 上一篇:控制流:if / for / while。
定义与调用
def greet(name):
"""给指定的人打招呼。""" # 文档字符串
print(f"你好,{name}!")
greet("小明") # 输出: 你好,小明!
greet("小红") # 输出: 你好,小红!
拆开看:def 声明要定义函数,greet 是函数名,name 是参数,冒号加缩进体就是函数要做的事。定义时函数体不会执行,只有调用时才执行。
返回值
用 return 把结果交回给调用者。没有 return 的函数返回 None。
def add(a, b):
return a + b
print(add(3, 5)) # 输出: 8
print(type(add(3, 5))) # 输出: <class 'int'>
return 一旦执行,函数立刻结束;返回多个值其实是返回一个元组:
def check(n):
if n < 0:
return "负数"
return "非负数" # 上面的分支返回后,这里不会被执行
def min_max(nums):
return min(nums), max(nums) # 返回元组
low, high = min_max([3, 1, 4, 1, 5])
print(check(-5), low, high) # 输出: 负数 1 5
参数的三种形态
位置参数
按顺序一一对应:
def intro(name, age):
print(f"我是{name},{age}岁")
intro("小明", 18) # 输出: 我是小明,18岁
默认参数
给参数一个默认值,调用时不传就用默认的。默认参数必须放在位置参数后面。
def greet(name, greeting="你好"):
print(f"{greeting},{name}")
greet("小明") # 输出: 你好,小明
greet("小红", "早上好") # 输出: 早上好,小红
# ❌ SyntaxError: non-default argument follows default argument
# def f(a=1, b): ...
关键字参数
调用时用 名字=值,可以打乱顺序、提升可读性;混用时位置参数在前,关键字参数在后。
def register(name, age, city):
print(f"{name}, {age}, {city}")
register("小明", 18, "北京") # 位置参数
register(city="上海", name="小红", age=20) # 关键字参数,顺序随意
# 输出: 小红, 20, 上海
# f(a=1, 2, 3) # ❌ SyntaxError:位置参数不能跟在关键字参数后面
args 与 *kwargs
当参数个数不确定时,用它们收集(名字可以换,星号不能少):
def total(*args):
print(args) # args 是一个元组
return sum(args)
print(total(1, 2, 3)) # 输出: (1, 2, 3) 然后 6
def show(**kwargs):
print(kwargs) # kwargs 是一个字典
for key, value in kwargs.items():
print(f"{key} = {value}")
show(name="小明", age=18)
# 输出: {'name': '小明', 'age': 18} 然后 name = 小明 / age = 18
顺序固定为"位置 → *args → **kwargs",调用时还能反向拆包:
def func(a, b, *args, **kwargs):
print(a, b, args, kwargs)
func(1, 2, 3, 4, x="hello") # 输出: 1 2 (3, 4) {'x': 'hello'}
print(total(*[1, 2, 3])) # * 把列表拆成位置参数,输出: (1, 2, 3) 然后 6
show(**{"name": "小明"}) # ** 把字典拆成关键字参数
作用域与 LEGB
函数内部定义的变量,外面看不见;但函数可以读外部变量:
def f():
inner = "我只在函数里存在"
print(inner)
f() # 输出: 我只在函数里存在
# print(inner) # ❌ NameError
message = "全局变量"
def show_msg():
print(message) # 读取全局变量,没问题
想在函数里修改全局变量,得用 global 声明:
counter = 0
def bump():
global counter # 声明"我要改的是全局那个 counter"
counter += 1
bump(); bump()
print(counter) # 输出: 2
def bump_wrong():
counter = 100 # 不加 global,这只是一个局部变量
bump_wrong()
print(counter) # 输出: 0,全局没变
LEGB 查找顺序
Python 找变量名按 L → E → G → B:Local(当前函数内部)→ Enclosing(外层函数/闭包)→ Global(模块全局)→ Built-in(内置,如 print、len)。
x = "全局"
def outer():
x = "外层"
def inner():
x = "内层"
print(x) # 就近取"内层"
inner()
outer() # 输出: 内层
实践建议:尽量少用 global,它会让代码难以追踪。更好的做法是用参数传入、用返回值传出。
文档字符串
函数的第一行字符串,就是它的说明文档:
def area(width, height):
"""计算矩形面积。
参数: width 宽, height 高
返回: 面积(宽 × 高)
"""
return width * height
print(area(3, 4)) # 输出: 12
print(area.__doc__) # 打印上面那段说明(交互式里也可用 help(area))
把代码"装进函数"的思维
# ❌ 平铺直叙,想复用就得复制粘贴
print("你好,小明!欢迎回来。")
print("你好,小红!欢迎回来。")
# ✅ 提成函数,逻辑只有一份
def welcome(name):
print(f"你好,{name}!欢迎回来。")
for name in ["小明", "小红", "小刚"]:
welcome(name)
判断标准很简单:同一段逻辑你复制了第二次,就该提成函数。
递归入门
函数调用自己,叫递归。关键是两个要素:基线条件(什么时候停下)和递归调用(把问题缩小一点)。
阶乘:n! = n × (n-1)!,且 0! = 1。
def factorial(n):
if n <= 1: # 基线条件
return 1
return n * factorial(n - 1) # 递归调用
print(factorial(5)) # 输出: 120(5*4*3*2*1)
print(factorial(0)) # 输出: 1
factorial(5) 会展开成 5 * (4 * (3 * (2 * 1))) 再逐层返回。递归适合表达清晰的结构,但要注意递归深度和重复计算——朴素斐波那契递归就慢得离谱,进阶篇会用 functools.lru_cache 优化它。
常见坑
坑一:默认参数用可变对象
# ❌ 默认值是列表,会在多次调用间"共享"并累积
def add_item(item, box=[]):
box.append(item)
return box
print(add_item("a")) # 输出: ['a']
print(add_item("b")) # 输出: ['a', 'b'] 意外吧!
# ✅ 用 None 作哨兵,每次重新创建
def add_item(item, box=None):
if box is None:
box = []
box.append(item)
return box
print(add_item("a"), add_item("b")) # 输出: ['a'] ['b']
坑二:忘了 return
# ❌ 想返回结果却忘了写 return
def add(a, b):
a + b
print(add(2, 3)) # 输出: None
# ✅
def add(a, b):
return a + b
坑三:函数里改全局忘了 global
count = 0
# ❌ UnboundLocalError:想改全局却被当成本地
# def inc():
# count += 1
# ✅
def inc():
global count
count += 1
坑四:递归没有基线条件
# ❌ 无限递归,最后 RecursionError
# def f(n):
# return f(n - 1)
# ✅ 一定要有能停下来的分支
def f(n):
return 0 if n <= 0 else f(n - 1)
小结
- 用
def定义、函数名(参数)调用;函数体只在调用时执行。 return交回结果,没有它就返回None。- 参数分位置、默认、关键字;默认参数要放最后。
*args收位置参数成元组,**kwargs收关键字参数成字典;调用时*/**可反向拆包。- 变量查找顺序 LEGB:局部 → 外层 → 全局 → 内置;改全局要
global(但尽量少用)。 - 写 docstring 是基本礼貌。
- 同一逻辑出现两次就提成函数。
- 递归要有基线条件和缩小问题,注意防止无限递归。
延伸阅读
- 03-装饰器:从函数到框架:函数作为一等公民的进阶玩法
- 26-字符串与格式化:函数处理文本
上一篇:控制流:if / for / while · 下一篇:字符串与格式化
文章回复
0 条公开回复