当前位置:首页>python>Python 零基础100天—Day14 函数进阶

Python 零基础100天—Day14 函数进阶

  • 2026-10-11 08:02:08
Python 零基础100天—Day14 函数进阶

🐍 函数进阶 — 从会写到写好

🕐 预计用时:2-3 小时 | 🎯 目标:掌握 *args/**kwargs、作用域、lambda 和闭包基础


📖 今日目录

  1. *args:接收任意数量的位置参数
  2. **kwargs:接收任意数量的关键字参数
  3. 参数组合的完整规则
  4. 变量作用域(LEGB 规则)
  5. global 和 nonlocal
  6. lambda 匿名函数
  7. 闭包(Closure)入门
  8. 实战练习
  9. 今日小结

1. *args:接收任意数量的位置参数

有时候你不知道调用者会传几个参数——用 *args 来"打包"接收。

# 普通函数:参数数量固定
def add(a, b):
    return a + b

print(add(1, 2))       # 3
# print(add(1, 2, 3))  # ❌ TypeError

# *args:参数数量不固定
def add_all(*args):
    print(f"args = {args}")
    print(f"type = {type(args)}")  # tuple!
    return sum(args)

print(add_all(1, 2))          # args = (1, 2) → 3
print(add_all(1, 2, 3))       # args = (1, 2, 3) → 6
print(add_all(1, 2, 3, 4, 5)) # args = (1, 2, 3, 4, 5) → 15

💡 *args 本质上是一个元组!
Python 把多余的位置参数打包成元组,赋值给 args 这个变量名。
变量名不一定要叫 args,*numbers、*items 都行,* 才是关键。

📦 常见用法

# 日志函数:接收任意消息
def log(*messages):
    for msg in messages:
        print(f"[LOG] {msg}")

log("服务器启动", "端口8080", "等待连接")
# [LOG] 服务器启动
# [LOG] 端口8080
# [LOG] 等待连接

# 求平均值
def average(*numbers):
    return sum(numbers) / len(numbers)

print(average(80, 90, 85))    # 85.0
print(average(100, 95, 88, 92, 78))  # 90.6

# 打印函数
def print_info(name, *hobbies):
    print(f"{name} 的爱好:")
    for h in hobbies:
        print(f"  - {h}")

print_info("张三", "编程", "游泳", "看书")
# 张三 的爱好:
#   - 编程
#   - 游泳
#   - 看书

🔓 解包:把列表/元组展开为参数

def add(a, b, c):
    return a + b + c

nums = [1, 2, 3]
print(add(*nums))  # 6(等价于 add(1, 2, 3))

# * 解包列表/元组为位置参数
def show(*args):
    print(args)

show(*[1, 2, 3])    # (1, 2, 3)
show(*(4, 5, 6))    # (4, 5, 6)
show(*"hello")      # ('h', 'e', 'l', 'l', 'o')

2. **kwargs:接收任意数量的关键字参数

**kwargs 接收"多余的关键字参数",打包成字典。

def print_profile(**kwargs):
    print(f"kwargs = {kwargs}")
    print(f"type = {type(kwargs)}")  # dict!
    for key, value in kwargs.items():
        print(f"  {key}: {value}")

print_profile(name="张三", age=25, city="北京")
# kwargs = {'name': '张三', 'age': 25, 'city': '北京'}
#   name: 张三
#   age: 25
#   city: 北京

📦 常见用法

# 配置函数:接收任意配置项
def create_server(**config):
    host = config.get("host", "127.0.0.1")
    port = config.get("port", 8080)
    debug = config.get("debug", False)
    print(f"启动服务器: {host}:{port} (debug={debug})")

create_server()                          # 默认配置
create_server(port=3000, debug=True)     # 自定义配置

# 合并字典
def merge(*dicts):
    result = {}
    for d in dicts:
        result.update(d)
    return result

defaults = {"color": "red", "size": "medium"}
custom = {"size": "large", "weight": "bold"}
print(merge(defaults, custom))
# {'color': 'red', 'size': 'large', 'weight': 'bold'}

🔓 解包:把字典展开为关键字参数

def greet(name, age, city):
    print(f"{name}, {age}岁, {city}")

info = {"name": "张三", "age": 25, "city": "北京"}
greet(**info)  # 等价于 greet(name="张三", age=25, city="北京")

# ** 解包字典为关键字参数
def show(**kwargs):
    print(kwargs)

show(**{"a": 1, "b": 2})  # {'a': 1, 'b': 2}
show(**dict(x=10, y=20))  # {'x': 10, 'y': 20}

3. 参数组合的完整规则

Python 参数有严格的顺序,记住这个公式:

def func(pos_only, /, normal, *args, kw_only, **kwargs):
    pass
#         ↑         ↑      ↑       ↑         ↑
#     位置专属    普通参数  可变位置  关键字专属  可变关键字

📋 参数类型速查表

参数类型
语法
传入方式
位置专属
a, b, /
只能按位置
普通参数
a, b
位置或关键字
*args
*args
多余的位置参数打包
关键字专属
*, a, b
只能按关键字
**kwargs
**kwargs
多余的关键字参数打包
# 完整示例
def full_example(a, b, /, c, *args, d, e=100, **kwargs):
    print(f"a={a}, b={b}, c={c}")
    print(f"args={args}")
    print(f"d={d}, e={e}")
    print(f"kwargs={kwargs}")

full_example(1, 2, 3, 4, 5, d=6, e=7, x=8, y=9)
# a=1, b=2, c=3
# args=(4, 5)
# d=6, e=7
# kwargs={'x': 8, 'y': 9}

💡 日常开发中最常用的组合:
1. def f(a, b, *args) — 固定参数 + 可变位置参数
2. def f(a, b, **kwargs) — 固定参数 + 可变关键字参数
3. def f(*args, **kwargs) — 万能签名(接收一切)


4. 变量作用域(LEGB 规则)

Python 找变量的顺序:L → E → G → B,就像找钥匙一样——先翻自己的口袋,再找邻居的。

层级
名称
说明
L
Local
函数内部定义的变量
E
Enclosing
外层函数(闭包场景)
G
Global
模块顶层定义的变量
B
Built-in
Python 内置的(len, print, range...)
x = "全局变量"           # G: Global

def outer():
    x = "外层变量"       # E: Enclosing

    def inner():
        x = "内部变量"   # L: Local
        print(x)

    inner()              # 内部变量

outer()
print(x)                 # 全局变量(不受函数影响)

🔍 LEGB 查找过程

# L → E → G → B 的查找
name = "全局张三"

def test():
    # name = "局部张三"    # 如果取消注释,优先用局部的
    print(name)             # 找到全局的 → 全局张三

test()

# 内置函数的 B 层
# len 就是 B 层的变量
print(len([1, 2, 3]))      # 3(找到内置的 len 函数)

⚠️ 作用域陷阱:

x = 10

def test():
    print(x)    # ❌ UnboundLocalError!
    x = 20

# 原因:函数内有 x = 20 的赋值,Python 认为 x 是局部变量
# 但 print(x) 在赋值之前,所以报错

解决方法:用 global 声明,或者不要在函数内同名赋值。


5. global 和 nonlocal

🌐 global:在函数内修改全局变量

counter = 0               # 全局变量

def increment():
    global counter         # 声明:我要用全局的 counter
    counter += 1
    print(f"计数器: {counter}")

increment()   # 计数器: 1
increment()   # 计数器: 2
increment()   # 计数器: 3
print(counter) # 3(全局变量被修改了)
# 不用 global 的情况
name = "张三"

def change_name():
    name = "李四"       # 这是创建了一个局部变量,不是修改全局的
    print(f"函数内: {name}")

change_name()           # 函数内: 李四
print(f"函数外: {name}") # 函数外: 张三 ← 没变!

🔗 nonlocal:在内层函数修改外层函数的变量

def outer():
    count = 0            # 外层函数的局部变量

    def inner():
        nonlocal count   # 声明:我要用外层的 count
        count += 1
        print(f"inner: {count}")

    inner()   # inner: 1
    inner()   # inner: 2
    inner()   # inner: 3
    print(f"outer: {count}")  # outer: 3

outer()
关键字
作用
使用场景
global
在函数内声明使用全局变量
计数器、配置修改
nonlocal
在内层函数声明使用外层函数的变量
闭包、装饰器

💡 能不用 global 就不用!
大量使用 global 会让代码难以理解和调试。
更好的做法:用参数传递、用 return 返回结果。


6. lambda 匿名函数

lambda 是"一行函数"——临时用一下,不需要正式定义。

# 普通函数
def double(x):
    return x * 2

# 等价的 lambda
double = lambda x: x * 2

print(double(5))   # 10

# lambda 的语法:lambda 参数: 表达式
# 只能写一行表达式,不能写多行语句

# 多个参数
add = lambda a, b: a + b
print(add(3, 5))   # 8

# 带默认值
greet = lambda name, msg="你好": f"{msg}, {name}!"
print(greet("张三"))            # 你好, 张三!
print(greet("张三", "早上好"))  # 早上好, 张三!

🎯 lambda 的常见用法

# 1. 排序的 key 参数
students = [("张三", 85), ("李四", 92), ("王五", 78)]
students.sort(key=lambda s: s[1])          # 按成绩排序
print(students)  # [('王五', 78), ('张三', 85), ('李四', 92)]

students.sort(key=lambda s: -s[1])         # 按成绩降序
print(students)  # [('李四', 92), ('张三', 85), ('王五', 78)]

# 2. sorted() 的 key
words = ["banana", "apple", "cherry", "date"]
by_length = sorted(words, key=lambda w: len(w))
print(by_length)  # ['date', 'apple', 'banana', 'cherry']

# 3. 字典按值排序
scores = {"张三": 85, "李四": 92, "王五": 78}
ranked = sorted(scores.items(), key=lambda x: -x[1])
print(ranked)  # [('李四', 92), ('张三', 85), ('王五', 78)]

# 4. 条件表达式
classify = lambda x: "正数" if x > 0 else ("零" if x == 0 else "负数")
print(classify(5))    # 正数
print(classify(-3))   # 负数
print(classify(0))    # 零

💡 lambda vs def 怎么选?
lambda:简单的一行表达式,临时用(排序 key、map 的函数)
def:复杂的逻辑、需要文档、需要多次调用

经验法则:如果逻辑超过一行,用 def。


7. 闭包(Closure)入门

闭包 = 内层函数 + 记住了外层变量。即使外层函数已经执行完毕,内层函数仍然能访问那些变量。

def make_multiplier(n):
    """创建一个乘以 n 的函数"""
    def multiplier(x):
        return x * n     # n 来自外层函数
    return multiplier

# 创建两个"记住"不同 n 的函数
double = make_multiplier(2)    # n=2 被"记住"了
triple = make_multiplier(3)    # n=3 被"记住"了

print(double(5))   # 10(5 × 2)
print(triple(5))   # 15(5 × 3)

# make_multiplier 已经执行完了,但 n 还活在闭包里!
print(double.__closure__[0].cell_contents)  # 2
print(triple.__closure__[0].cell_contents)  # 3

🎯 闭包的实际用法

# 1. 计数器工厂
def make_counter(start=0):
    count = start
    def counter():
        nonlocal count
        count += 1
        return count
    return counter

c1 = make_counter()
c2 = make_counter(100)

print(c1())  # 1
print(c1())  # 2
print(c1())  # 3
print(c2())  # 101
print(c2())  # 102

# 2. 缓存函数(简易版)
def make_cache():
    cache = {}
    def cached_func(x):
        if x not in cache:
            print(f"计算 {x}...")
            cache[x] = x ** 2
        return cache[x]
    return cached_func

square = make_cache()
print(square(5))   # 计算 5... → 25
print(square(3))   # 计算 3... → 9
print(square(5))   # 25(直接返回缓存,不计算)

# 3. 权限检查器
def make_checker(required_role):
    def check(user_role):
        return user_role == required_role
    return check

is_admin = make_checker("admin")
is_editor = make_checker("editor")

print(is_admin("admin"))    # True
print(is_admin("user"))     # False
print(is_editor("editor"))  # True

💡 闭包三要素:
1. 有嵌套函数(函数里定义函数)
2. 内层函数引用了外层函数的变量
3. 外层函数返回内层函数

闭包是装饰器的基础——Day 34 会深入学习!


8. 实战练习

🎯 练习 1:万能格式化器(*args + **kwargs)

def format_report(title, *items, **options):
    """
    生成格式化报告

    参数:
        title: 报告标题
        *items: 报告条目(任意数量)
        **options: 格式选项(width, separator, uppercase)
    """
    width = options.get("width", 40)
    separator = options.get("separator", "-")
    uppercase = options.get("uppercase", False)

    print(separator * width)
    print(title.center(width))
    print(separator * width)

    for i, item in enumerate(items, 1):
        text = f"{i}. {item}"
        if uppercase:
            text = text.upper()
        print(text)

    print(separator * width)

format_report("购物清单", "牛奶", "面包", "鸡蛋", "水果", width=30)
format_report("待办事项", "写代码", "开会", "运动", separator="=", uppercase=True)

🎯 练习 2:运算符工厂(闭包)

def make_operator(op):
    """根据运算符创建对应的函数"""
    operators = {
        "+": lambda a, b: a + b,
        "-": lambda a, b: a - b,
        "*": lambda a, b: a * b,
        "/": lambda a, b: a / b if b != 0 else "除数不能为零",
        "**": lambda a, b: a ** b,
    }

    if op not in operators:
        return None

    func = operators[op]

    def operate(a, b):
        result = func(a, b)
        print(f"{a} {op} {b} = {result}")
        return result

    return operate

add = make_operator("+")
mul = make_operator("*")
pow_func = make_operator("**")

add(10, 5)      # 10 + 5 = 15
mul(10, 5)      # 10 * 5 = 50
pow_func(2, 10) # 2 ** 10 = 1024

🎯 练习 3:自动柯里化(闭包进阶)

def curry(func):
    """自动柯里化:把多参数函数变成链式调用"""
    import inspect
    params = inspect.signature(func).parameters
    n = len(params)

    def collect_args(args_so_far):
        def inner(*new_args):
            all_args = args_so_far + new_args
            if len(all_args) >= n:
                return func(*all_args[:n])
            return collect_args(all_args)
        return inner

    return collect_args(())

# 使用
@curry
def add3(a, b, c):
    return a + b + c

# 三种调用方式
print(add3(1, 2, 3))     # 6(一次传完)
print(add3(1)(2, 3))     # 6(分两次)
print(add3(1)(2)(3))     # 6(链式调用)

9. 今日小结

知识点
核心内容
*args
打包多余位置参数为元组
**kwargs
打包多余关键字参数为字典
参数顺序
位置专属 / 普通 *args 关键字专属 **kwargs
解包
*list
 展开为位置参数,**dict 展开为关键字参数
LEGB
Local → Enclosing → Global → Built-in
global
在函数内修改全局变量(少用为妙)
nonlocal
在内层函数修改外层函数变量
lambda
一行匿名函数,常用于排序 key
闭包
内层函数 + 记住外层变量 = 装饰器基础

🧠 记忆口诀:
星号打包 args 收,双星 kwargs 字典留。
LEGB 四层往外找,global 少用保平安。
lambda 一行够简洁,闭包装饰器在眼前。

🔮 预告: Day 15 内置函数 — map/filter/reduce、zip/enumerate、sorted/reversed、any/all。函数式编程的第一步!

轻松时刻:

请在微信客户端打开

最新文章

随机文章