RSS
菜单
全部文章快讯开发科技深度热点

函数基础(Python 从精通到入门 · 25)

内容摘要

函数是给一段逻辑起名字。你一旦学会"把代码装进函数",就再也不想写一大坨平铺直叙的脚本了。

函数是给一段逻辑起名字。你一旦学会"把代码装进函数",就再也不想写一大坨平铺直叙的脚本了。

前面几篇的代码越长越长,是时候把它们收拾整理了。函数就是那个收纳盒:给一段逻辑起个名字,下次直接用名字调用。

你将学到

  • 定义与调用函数,参数与返回值
  • 位置参数、默认参数、关键字参数的区别与用法
  • *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 · 下一篇:字符串与格式化

— 全文完 —回到顶部 ↑
下载推广海报

文章推广海报

《函数基础(Python 从精通到入门 · 25)》完整推广海报
DISCUSSION

文章回复

0 条公开回复
未登录回复需要审核后公开
还没有回复,欢迎参与讨论。