Skip to content

Python 函数、模块与包 ​

定义、参数与返回值 ​

用 def 定义函数,调用时才执行函数体。return 返回结果;省略 return 或只写 return 都返回 None。把计算与打印分开,更容易复用与测试。

python
def greet(name, greeting="Hello", *, punctuation="!"):
    """生成问候文本;punctuation 只接受关键字传参。"""
    return f"{greeting}, {name}{punctuation}"


assert greet("Alice") == "Hello, Alice!"
assert greet(name="Bob", greeting="Hi", punctuation=".") == "Hi, Bob."

必需参数不能省略,同一个参数不能重复赋值。*args 收集额外位置实参,**kwargs 收集额外关键字实参;只在函数确实需要可变参数时使用。

python
def total(*numbers):
    return sum(numbers)


assert total(1, 2, 3) == 6

默认值与对象引用 ​

默认值在定义函数时求值一次。需要每次调用得到新容器时,用 None 作为默认值,而不是 []。传参不会自动复制对象:修改传入列表会影响调用方,重新绑定局部名称则不会。

python
def add_item(item, items=None):
    if items is None:
        items = []
    items.append(item)
    return items


assert add_item("a") == ["a"]
assert add_item("b") == ["b"]

函数先查局部、外围函数、模块全局、内置名称。尽量通过参数和返回值传递数据,少依赖可变全局状态。官方函数教程说明了参数规则。

函数也是值与闭包 ​

函数可绑定给其他名称、作为参数传递,也可作为返回值。返回函数时,内层函数仍能访问外围函数中的绑定,这种关系称为闭包。

python
def make_prefixer(prefix):
    def prefix_text(text):
        return prefix + text
    return prefix_text


label = make_prefixer("note: ")
alias = label
assert alias("hello") == "note: hello"

make_prefixer 返回函数本身,label("hello") 才调用它。理解“接收一个函数、返回另一个函数”之后,再学 装饰器。

模块与执行入口 ​

一个 .py 文件可以作为模块。导入执行模块的顶层语句,通常随后缓存模块;不要在顶层做启动服务或读写文件等隐式操作。避免 from module import *,使名称来源清楚。

math_utils.py:

python
def double(value):
    return value * 2

app.py:

python
import sys
from math_utils import double


def main():
    value = int(sys.argv[1]) if len(sys.argv) > 1 else 21
    print(double(value))


if __name__ == "__main__":
    main()

在这两个文件所在目录运行 python app.py,输出 42。导入失败先确认模块位置、工作目录、解释器和依赖安装环境,而不是随意修改 sys.path。

命令行参数与模块入口 ​

继续使用上面的两个文件:

shell
python app.py
python app.py 7
python -m app 7

第一条输出 42,后两条输出 14。sys.argv 是命令行参数列表,首项通常是脚本名或模块文件路径;上例把第一个参数转换成整数,省略参数时使用 21。输入非整数会抛异常,处理方式在 异常课学习。

__name__ 判断使 import app 时不会执行命令行入口。运行 -m app 时,应位于可找到该模块的目录。不要命名为 json.py、random.py 等,以免遮蔽标准库。

包结构 ​

包组织多个模块。普通包包含 __init__.py;命名空间包可以没有它,所以“任何包都必须有此文件”并不准确。初学项目先用普通包即可。

text
project/
  toolbox/
    __init__.py
    math_utils.py
    app.py

在 toolbox/app.py 使用 from .math_utils import double,从 project 目录执行 python -m toolbox.app。直接执行该文件可能没有包上下文,导致相对导入失败。参见 模块与包教程。

练习 ​

把去空白、过滤空字符串的逻辑写成 clean_lines(lines),添加空输入和普通输入断言,再拆成模块导入。下一步读 数据结构。

参考解答 ​

clean_lines.py 的完整参考如下,导入只定义函数:

python
def clean_lines(lines):
    return [line.strip() for line in lines if line.strip()]

同目录 check_lines.py:

python
from clean_lines import clean_lines


if __name__ == "__main__":
    assert clean_lines([]) == []
    assert clean_lines([" a ", " ", "b\n"]) == ["a", "b"]

提示:保留输入顺序,这个函数不排序也不去重。预期 python check_lines.py 正常结束且没有输出;错误断言才失败。文件名与导入名一致,从该目录执行,参考未运行。

继续阅读