简介:函数与模块是Python学习中承上启下的关键环节。本文系统讲解函数定义、位置/关键字/默认参数、*args与**kwargs、递归三要素,以及模块导入方式、常用内置模块、自定义模块与__name__变量的用法,帮助初学者告别冗长脚本,写出结构清晰、可复用、易维护的代码。
很多初学者在掌握变量、条件判断、循环之后会遇到瓶颈:脚本越写越长,重复代码越堆越多,改一处错一处。这个阶段的“解药”正是函数与模块,它们分别对应两类核心能力:
理解了这两点,就理解了几乎所有编程语言共通的工程化思路。下面我们逐一拆解。
def greet(name):"""向指定的人打招呼"""return f"你好,{name}!"message = greet("小明")print(message) # 你好,小明!
几个要点需要牢记:
def make_coffee(kind, size="中杯", sugar=0):return f"{size}{kind},加{sugar}份糖"print(make_coffee("拿铁")) # 全部使用默认值print(make_coffee("美式", sugar=2)) # 关键字参数print(make_coffee("摩卡", "大杯", 1)) # 位置参数
新手最容易踩的坑是:默认参数使用可变对象。请看反例:
def add_item(item, items=[]): # 危险写法items.append(item)return itemsprint(add_item(1)) # [1]print(add_item(2)) # [1, 2],默认列表被两次调用共享了!
原因在于默认值在函数定义时只创建一次。正确做法是把默认值设为 None,在函数体内新建:
def add_item(item, items=None):if items is None:items = []items.append(item)return items
当函数需要接受任意数量的参数时:
*args 把多余的位置参数收集成一个元组;**kwargs 把多余的关键字参数收集成一个字典。
def report(title, *scores, **details):print(f"标题:{title}")print(f"分数:{scores}")print(f"详情:{details}")report("月考", 90, 85, 88, subject="数学", cls="三班")
定义参数时的顺序约定为:位置参数 → args → 默认参数/关键字参数 → **kwargs。此外,调用函数时 `和**` 还能反向“解包”序列与字典,把容器中的元素展开传给函数,这在封装和转发参数的场景中非常常用。
递归适合解决“可以分解为同类子问题”的场景,例如计算阶乘:
def factorial(n):if n <= 1: # 基线条件:必须存在,否则无限递归return 1return n * factorial(n - 1) # 递归条件:规模不断缩小
写递归的三要素:
需要提醒的是,解释器对递归深度有默认限制(约千层级),深层递归应考虑改写为循环,或通过记录中间结果来避免重复计算。
import math # 导入整个模块print(math.sqrt(16)) # 4.0from datetime import date # 只导入需要的名字print(date.today())
import 模块名:命名空间清晰,不易与其他名字冲突,建议作为默认选择;from 模块 import 名字:调用简短,但应避免 from 模块 import * 这种写法,以免大量名字涌入当前文件,造成覆盖与混乱。标准库是随语言自带的“瑞士军刀”,初学者至少应熟悉以下三个:
math.sqrt() 开方、math.pi 圆周率、math.ceil() 向上取整;random.randint(a, b) 生成区间内随机整数、random.choice() 随机选取、random.shuffle() 打乱序列;datetime.now() 获取当前时间,配合 timedelta 做时间增减。
import randomfrom datetime import datetime, timedeltadice = random.randint(1, 6) # 掷骰子now = datetime.now()tomorrow = now + timedelta(days=1) # 明天此刻
养成“先查标准库再造轮子”的习惯,能省下大量时间,也让代码更可靠。
把函数保存为 my_utils.py,它就是一个模块,同目录下的其他文件通过 import my_utils 即可使用。而 __name__ 是每个模块自带的属性,其取值遵循一条简单规则:
__name__ 等于 "__main__";__name__ 等于模块名。利用这一点,可以让一个文件“既能当模块被导入,又能当脚本直接运行”:
# my_utils.pydef add(a, b):return a + bif __name__ == "__main__":# 只有直接运行本文件时才执行,被导入时不会执行print(add(1, 2))
这是社区约定俗成的代码组织方式,几乎所有成熟代码中都能看到这个结构。当项目继续变大,可以把多个模块放进同一个文件夹并配置 __init__.py,形成“包”,按“包名.模块名”的层级管理代码,目录结构即程序结构。
utils.py,收纳常用工具函数,体会模块化带来的整洁;函数让你复用逻辑,模块让你组织代码。掌握 def 与三类参数的传递规则,理解 args/*kwargs 的收集与解包,写递归时先想清楚终止条件;再用 import 管理依赖,用 name 区分脚本与模块——这些能力共同构成了从“写代码”迈向“做工程”的桥梁。夯实这一阶段后,可以继续学习面向对象与异常处理,让程序结构更上一层楼。