Python函数详解:从语法到参数传递的思考

Python 函数详解:从语法到参数传递的思考

函数是 Python 中最核心、最常用的特性之一。掌握函数,不仅能写出可复用的代码,更能深刻理解 Python 的“一切皆对象”和参数传递机制。本文从基础语法开始,逐步深入到参数传递的本质,最后给出实际思考和最佳实践。

1. 函数的基本语法

定义函数

def 函数名(参数列表):
    """文档字符串(可选,但强烈推荐)"""
    # 函数体
    语句...
    return 返回值  # 可选,不写则返回 None

示例

def greet(name: str) -> str:
    """问候函数"""
    return f"你好,{name}!"

print(greet("小明"))  # 你好,小明!
  • 类型提示name: str, -> str):Python 3.5+ 引入,属于注解,不强制执行,但 IDE 和类型检查工具(如 mypy)会使用。
  • 文档字符串:用三引号,help(greet) 或 IDE 会显示。

调用函数

result = greet("小明")

2. 参数的种类(从简单到高级)

Python 函数参数非常灵活,支持多种传递方式。参数顺序规则(Python 3.8+):

**位置参数(Positional-only) / 普通参数 / * 可变位置参数 / 仅关键字参数 / ** 可变关键字参数**

(1) 位置参数与关键字参数

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

add(3, 5)           # 位置参数
add(a=3, b=5)       # 关键字参数
add(b=5, a=3)       # 关键字参数顺序可变

(2) 默认参数

默认参数必须放在非默认参数之后。

def power(base, exp=2):
    return base ** exp

power(3)      # 9
power(3, 4)   # 81

(3) 可变参数:*args 和 **kwargs

def print_info(title, *args, **kwargs):
    print("标题:", title)
    print("位置参数:", args)      # tuple
    print("关键字参数:", kwargs)  # dict

print_info("测试", 1, 2, 3, name="张三", age=18)

输出:

标题: 测试
位置参数: (1, 2, 3)
关键字参数: {'name': '张三', 'age': 18}

(4) Python 3.8+ 新特性:位置仅参数(/)和仅关键字参数(*)

def modern_func(a, b, /, c, *, d=10):
    # a, b 必须用位置方式传入
    # c 可以位置或关键字
    # d 必须用关键字
    pass

modern_func(1, 2, 3, d=20)   # 正确
# modern_func(1, 2, c=3)     # 正确
# modern_func(a=1, b=2, c=3) # 错误!a,b 不能用关键字

这个特性在库设计中非常有用(如 divmod()range()),能强制调用者使用更清晰的方式。

3. 参数传递机制:核心思考点

Python 的参数传递本质是“按对象引用传递”(Pass by Object Reference),或者更准确地说是 “按赋值传递”(Pass by Assignment)

Python 中一切都是对象。函数调用时,参数名只是对传入对象的引用(指针),类似赋值操作 param = argument

关键区别:可变对象 vs 不可变对象

类型示例修改行为对外部影响
不可变int, float, str, tuple, frozenset创建新对象无影响
可变list, dict, set, 自定义类实例原地修改(in-place)有影响

实验验证

def modify_list(lst):
    lst.append(4)      # 原地修改可变对象

l = [1, 2, 3]
modify_list(l)
print(l)   # [1, 2, 3, 4]  ← 外部被修改!
def modify_int(x):
    x = 10             # 重新绑定局部变量

n = 5
modify_int(n)
print(n)   # 5  ← 外部不变!

为什么会这样?

  • lst.append(4):操作的是列表对象本身,lst 和外部 l 指向同一个对象。
  • x = 10:重新让局部变量 x 指向一个新的整数对象 10,外部 n 仍然指向 5。

这不是传统的“传值”也不是“传引用”,而是 传引用但赋值时重新绑定

思考

  • 优点:高效!大对象(几 GB 的 DataFrame)不需要拷贝,直接传递引用。
  • 缺点:容易产生副作用(side effects),尤其在多线程或大型项目中。
  • 设计哲学:Python 相信程序员的责任感(“We are all consenting adults here”),不强制拷贝,但要求你了解对象可变性。

4. 常见陷阱与最佳实践

陷阱1:可变默认参数(最经典的坑!)

def bad_func(a=[]):
    a.append(1)
    return a

print(bad_func())  # [1]
print(bad_func())  # [1, 1]
print(bad_func())  # [1, 1, 1]

原因:默认参数在函数定义时只计算一次,可变对象会被所有调用共享。

正确写法

def good_func(a=None):
    if a is None:
        a = []          # 每次调用新建列表
    a.append(1)
    return a

陷阱2:不必要的拷贝

# 坏:大列表每次都拷贝
def process(data):
    data = data[:]   # 浅拷贝

# 好:明确告诉调用者会修改
def process_inplace(data):
    data.append("new")
    # 或返回新对象
    return [x*2 for x in data]

最佳实践建议

  • 修改可变对象时:要么原地修改并返回 None,要么返回新对象(函数式风格)。
  • 团队协作:函数名或文档明确说明是否会修改参数(inplace=True 风格)。
  • 大型项目:优先使用不可变数据结构(tuplefrozendictpydantic 等)或返回新对象。

5. 进阶思考:函数设计的哲学

  1. Python 函数是一等公民:可以赋值、作为参数、作为返回值、放在容器里。
  2. 闭包与作用域(LEGB 规则):函数可以捕获外部变量,形成闭包。
  3. 装饰器:本质是函数包裹函数。
   def timer(func):
       def wrapper(*args, **kwargs):
           import time
           start = time.time()
           result = func(*args, **kwargs)
           print(f"耗时: {time.time()-start:.4f}s")
           return result
       return wrapper

   @timer
   def slow_func():
       time.sleep(1)
  1. 函数式编程倾向:鼓励 mapfilterlambdafunctools 等,减少副作用。

总结

  • 语法:灵活 + 类型提示 + 文档字符串 = 现代 Python 函数。
  • 参数传递按对象引用传递 → 可变对象有副作用,不可变对象安全。
  • 核心思考:Python 把控制权交给程序员,同时要求程序员理解对象模型。

掌握了参数传递机制,你就真正理解了“Pythonic”的精髓:简洁、高效、但需要对对象有敬畏之心

想深入哪个部分(装饰器、生成器、异步函数、类型提示进阶等)?随时告诉我,我可以继续展开!

文章已创建 4543

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部