跳到正文
本地阅读 · 函数、作用域与参数

函数、作用域与参数

计算机基础 · Python基础

前置知识:容器、字符串与切片

本章目录

本章学习目标

  • 用函数定义明确的数据处理接口
  • 理解参数绑定、作用域与可变默认值
  • 分离计算、输入输出和边界校验

一、函数把规则变成可重复接口

函数接收参数、执行规则并用 return 返回结果。它的价值不是简单缩短代码,而是把输入约束和输出约定集中到一个地方。订单净额函数可以约定接收 amount 和 refund,返回非负金额;遇到缺失或退款超过金额时抛 ValueError。调用者无需知道内部临时变量,只需要理解接口和异常。只打印结果而不 return,会让函数返回 None,后续无法继续聚合。

划分函数时让每个函数回答一个清楚的问题:清洗状态、计算净额、聚合用户,分别承担不同规则。一个函数同时读取文件、修改全局表、画图、连接数据库和返回统计值,会使测试与复用困难。类型注解与 docstring 有助于阅读,但 Python 不会因为写了 amount: float 就自动拒绝字符串;正式校验仍要在代码中实施。说明单位和缺失处理比单独写类型名更重要。

二、参数传递是名字绑定

调用函数时参数名字绑定到传入对象。对于列表,函数内部执行 append 会修改调用者看到的同一对象;但执行 items=[] 只让局部名字指向新列表,不会把调用者原来的变量重新绑定。把机制称为“所有参数都是传值”或“所有参数都是传引用”都容易误导,关键是区分对象修改与名字重新赋值。

函数应明确是否原地修改输入。数据清洗通常优先返回新结果,降低对原始数据的意外影响。若为了性能确实原地修改,就在名称或说明中标记,并测试调用者是否接受。相同原则会贯穿 NumPy 视图和 pandas 赋值章节。纯计算函数不依赖当前目录和全局状态,相同输入得到相同结果,验证起来更直接;需要随机数时把随机生成器或种子作为显式依赖。

三、默认参数和关键字参数

默认参数在函数定义时求值,而不是每次调用时创建。def collect(x,items=[]): items.append(x); return items 会让多次调用共享同一列表;第一次得到 [1],第二次得到 [1,2],常与新手预期不符。正确做法使用 None 作为默认值,并在函数体内新建列表。不可变默认值如数字、字符串通常更安全,但仍应避免隐藏的全局依赖。

关键字参数使调用更容易理解,例如 net_amount(100,refund=20)。星号可以让后续参数只能按关键字传入,减少多个相同类型参数位置互换的错误;比如 def rate(success,total,*,empty=None)。*args 接收额外位置参数,**kwargs 接收额外关键字参数,但不应在初学项目里滥用,让接口无法知道自己支持什么。参数越明确,别人越容易正确使用和测试。

四、作用域与闭包的观察方法

函数内赋值的名字通常是局部变量;查找名字时从局部、外层函数、模块全局到内建空间进行。若函数体有 total+=x,解释器会把 total 当局部绑定,未初始化就读它会产生 UnboundLocalError。解决办法通常是传入值并返回新值,不是加 global 把所有状态放进模块。模块级常量可以共享,但可变全局表会让执行顺序影响结果。

闭包让内部函数使用外层变量,例如构造固定阈值的筛选器;返回函数后外层绑定仍可被访问。需要注意循环里创建的 lambda 可能在调用时才读取最终循环变量,造成所有函数返回同一值;可用默认参数固定当次值,或使用明确的工厂函数。这里不要求复杂函数式编程,但要能看出“什么时候求值”和“使用哪个作用域”的差别。

五、给函数建立输入和输出契约

先列出正常、空输入、缺失、负值、退款超额等情形,再写代码。教学函数用 ValueError 表示已知业务约束被违反,调用者可以选择报告问题而不是吞掉异常。平均值函数对空列表返回 None,净额函数对非法退款抛异常,这是明确的接口决定,不能把所有异常都转换成零。零金额是有效事实,异常值是数据质量问题。

配套脚本包含正常断言和异常断言,确保非法输入真的被拒绝。完成任务时先把核心计算写成函数,再在脚本末尾演示调用;这样同一函数以后可以从 pandas apply、命令行或 SQL 对照项目中复用。阅读函数代码时重点看参数、返回路径和副作用,尤其检查每个分支是否返回相同类型,避免一个分支是数值、另一个分支只是 print。

完整示例与实际输出

def net_amount(amount, *, refund=0):
    if amount is None or refund is None or not 0 <= refund <= amount:
        raise ValueError("amount/refund violate contract")
    return amount-refund

def collect(value, items=None):
    result=[] if items is None else list(items)
    result.append(value)
    return result

def mean_or_none(values):
    return sum(values)/len(values) if values else None

assert net_amount(100,refund=20)==80
assert collect(1)==[1] and collect(2)==[2]
original=[9]; changed=collect(1,original)
assert original==[9] and changed==[9,1]
assert mean_or_none([]) is None
try:
    net_amount(10,refund=11)
except ValueError:
    print("invalid refund rejected")
else:
    raise AssertionError("invalid input accepted")
print("net",net_amount(100,refund=20),"mean",mean_or_none([10,20]))

实际运行通过CPU Python execution.

invalid refund rejected
net 80 mean 15.0

五道自测:先回答,再核对

01 · 只有 print 没有 return 的函数返回什么?

查看提示

隐式返回。

查看参考答案

None。

02 · 为何不能用 [] 作为累计器默认参数?

查看提示

默认值求值时机。

查看参考答案

默认列表在定义时创建,多次调用共享同一对象。

03 · 函数内部 items=[] 是否重绑调用者变量?

查看提示

名字与对象区分。

查看参考答案

不会;只是局部名字重绑,但 append 原对象会影响调用者。

04 · 类型注解能自动校验输入吗?

查看提示

注解是描述还是执行?

查看参考答案

通常不能;需要显式校验或额外工具。

05 · 空样本均值返回零有什么问题?

查看提示

零和无观测不同。

查看参考答案

会把缺数据误解释为真实均值零,应采用明确缺失约定。

两项动手任务

任务 1 · 实现 conversion_rate(success,total),零分母返回 None,非法计数抛 ValueError。

提示

先验证 0<=success<=total。

完整参考解答与检查方式

接口明确区分无样本与非法计数。

def conversion_rate(success,total):
    if not 0<=success<=total:
        raise ValueError('invalid counts')
    return success/total if total else None
assert conversion_rate(2,4)==0.5
assert conversion_rate(0,0) is None
try:
    conversion_rate(3,2)
except ValueError:
    print('rejected')
else:
    raise AssertionError('must reject')
print(conversion_rate(2,4))

任务 2 · 写 filter_nonnegative(values),返回新列表且不修改输入,缺失值被过滤。

提示

不要原地删除遍历中的元素。

完整参考解答与检查方式

输出零和正数,原始列表保留负数与 None。

def filter_nonnegative(values):
    return [v for v in values if v is not None and v>=0]
original=[-1,None,0,3]
result=filter_nonnegative(original)
assert result==[0,3] and original==[-1,None,0,3]
print(result)

官方资料与掌握状态

完成两项任务后,按独立完成程度记录。状态会尽量保存在当前浏览器,可导出备份。

动手任务记录

掌握状态与复习

关掉资料独立完成后记 A;提示后完成记 B;需要重学记 C。

记录尽量保存在当前浏览器,建议定期导出备份。