Python 函数、模块与包
定义、参数与返回值
用 def 定义函数,调用时才执行函数体。return 返回结果;省略 return 或只写 return 都返回 None。把计算与打印分开,更容易复用与测试。
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 收集额外关键字实参;只在函数确实需要可变参数时使用。
def total(*numbers):
return sum(numbers)
assert total(1, 2, 3) == 6默认值与对象引用
默认值在定义函数时求值一次。需要每次调用得到新容器时,用 None 作为默认值,而不是 []。传参不会自动复制对象:修改传入列表会影响调用方,重新绑定局部名称则不会。
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"]函数先查局部、外围函数、模块全局、内置名称。尽量通过参数和返回值传递数据,少依赖可变全局状态。官方函数教程说明了参数规则。
函数也是值与闭包
函数可绑定给其他名称、作为参数传递,也可作为返回值。返回函数时,内层函数仍能访问外围函数中的绑定,这种关系称为闭包。
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:
def double(value):
return value * 2app.py:
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。
命令行参数与模块入口
继续使用上面的两个文件:
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;命名空间包可以没有它,所以“任何包都必须有此文件”并不准确。初学项目先用普通包即可。
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 的完整参考如下,导入只定义函数:
def clean_lines(lines):
return [line.strip() for line in lines if line.strip()]同目录 check_lines.py:
from clean_lines import clean_lines
if __name__ == "__main__":
assert clean_lines([]) == []
assert clean_lines([" a ", " ", "b\n"]) == ["a", "b"]提示:保留输入顺序,这个函数不排序也不去重。预期 python check_lines.py 正常结束且没有输出;错误断言才失败。文件名与导入名一致,从该目录执行,参考未运行。
继续阅读
- 开发实践
- 排错
- 开发实践