先说说你多半已经遇到的麻烦:程序里有一段代码要重复用好几遍,于是复制粘贴,粘贴完想改个地方,得挨个改好几处,漏改一处就是 bug。函数就是专门治这个的——把一段代码打包、起个名字,以后想用就叫它的名字,Python 会把那段代码从头跑一遍。就像微波炉,你不需要每次重新造一个加热的炉子,按一下"加热"按钮就行。
这一周后面要做文件读写、异常处理、学生信息管理系统,全靠函数把代码组织起来,所以今天的内容是后面所有章节的地基。我们从最基础的问题开始:怎么定义一个函数,怎么叫它干活。
定义函数:把代码打包起名字
定义函数用 def 开头,后面跟函数名和括号,再跟一个冒号;函数体要缩进 4 个空格,和 if、for 里的代码块一个规矩。调用函数就是写函数名加括号:函数名()。
先看这段,跑一下试试——
# 定义函数:def + 函数名 + 括号 + 冒号,函数体缩进
def greet():
print("你好,Python!")
# 调用函数:函数名 + 括号
greet()
greet()
greet()
输出长这样:
你好,Python!
你好,Python!
你好,Python!
函数体缩进不缩进,结果完全不同:不缩进会报 IndentationError(缩进错误),这是今天最常踩的坑,报错了就检查函数体是不是比 def 行多缩进了一截。
一个完整的小例子,把"欢迎语 + 分隔线"打包起来重复用:
def show_banner():
print("=" * 20)
print("欢迎来到学习系统")
print("=" * 20)
show_banner() # 调用一次
show_banner() # 想用几次调用几次
输出:
====================
欢迎来到学习系统
====================
====================
欢迎来到学习系统
====================
两个容易犯的错先说在前面,报错了别慌,对号入座:
- 调用时忘了写括号,写成
greet——不报错,但也不会执行,那只是"指向函数本身"。这个坑很隐蔽,因为程序安静地跑完了,什么都没发生。 - 调用写在定义前面——Python 从上往下读代码,还没见过
greet就去调它,报NameError。所以有条铁规矩:先定义,再调用,定义永远放在调用之前。
有人会问:这函数只会喊同一句口号,也太死板了吧。别急,参数就是让它活起来的办法。
参数:给函数"递东西"
def greet(name) 里的 name 叫形参(形式上的参数,占个位置);调用时写的 greet("小明") 里的 "小明" 叫实参(实际的参数,真实的数据)。函数拿到实参后,函数体里就能用 name 这个名字了。
可以这么记:形参是插座上的孔,实参是插进去的插头。孔告诉别人"这里能插什么形状",插头提供真实的电。函数可以有多个参数,用逗号隔开,调用时按顺序把值传进去——这叫位置参数(按位置一一对应)。
# 形参:name 是一个"占位"的名字
def greet(name):
print(f"你好,{name}!")
# 实参:调用时把真实的值传进去
greet("小明")
greet("小红")
greet("小刚")
输出:
你好,小明!
你好,小红!
你好,小刚!
多个参数时按顺序对应:第一个实参给第一个形参,第二个给第二个,以此类推。
def introduce(name, city, age):
print(f"我叫{name},来自{city},今年{age}岁。")
introduce("小明", "北京", 18)
introduce("小红", "上海", 19)
输出:
我叫小明,来自北京,今年18岁。
我叫小红,来自上海,今年19岁。
这里有两个坑:
- 参数个数对不上:定义了 3 个形参只传 2 个实参,报
TypeError: introduce() missing 1 required positional argument(缺少参数)。报错了就数一数括号里的逗号。 - 顺序搞反:
introduce("北京", "小明", 18)会把城市和姓名弄混。程序不会报错,你自己读一遍 f-string 输出的内容就能发现。
还有个小提醒:参数名和函数外面的变量重名没关系,参数是函数内部的局部名字,不用故意改名避开。
return:把结果"还"给你
有的函数只管做事(比如打印),有的函数要算出一个结果给你用。算结果靠 return:一碰到 return,函数立刻结束,把它后面的值送回调用处,你可以用变量接住。
def add(a, b):
result = a + b
return result # 把结果"还"给调用处
total = add(3, 5) # 用变量接住返回值
print(total) # 8
print(total * 2) # 返回值还能继续参与计算
输出:
8
16
判断什么时候用 return,有个简单的标准:调用处需不需要这个值继续算?需要就用 return,只是打印就用 print。下面这个对比能帮你彻底分清两件事:
def with_return(a, b):
return a + b
def no_return(a, b):
print(a + b)
x = with_return(3, 5)
y = no_return(3, 5)
print("x 的值:", x) # 8
print("y 的值:", y) # None!因为函数没有 return
输出:
8
x 的值: 8
y 的值: None
注意 y 是 None——没写 return 的函数,调用完拿到的是 None(什么都没有)。这是新手最容易懵的地方:明明函数里打印了 8,怎么变量是 None?因为打印是打印,返回是返回,两回事。return 结果 后面的代码不会执行,函数已经"下班"了。
和 return 有关的三个细节:
- 忘写 return:函数里只
print没return,调用处拿到None,后面拿它一运算就报TypeError。 - return 后面还有代码:那些代码永远不执行,Python 不会提醒你,写的时候自己留意。
- return 只执行一次:函数里第一个被碰到的
return生效;后面的return只有在前面的条件没触发时才有机会走到。
默认参数:不传就用默认值
有些参数大多数时候是同一个值,只有偶尔要换。这时候给它设个默认参数:定义时在形参后面写 参数名=默认值,调用时不传就用默认值,传了就按你传的来。好处是调用更省事,而且以后加新功能不破坏老代码。
def greet(name, word="你好"):
print(f"{word},{name}!")
greet("小明") # 没传 word,用默认值"你好"
greet("小红", "早上好") # 传了 word,用"早上好"
greet("小刚", "晚上好")
输出:
你好,小明!
早上好,小红!
晚上好,小刚!
再看一个贴近生活的例子——计算打折价:
def discount_price(price, off=0.8):
return price * off
print(discount_price(100)) # 默认打 8 折:80.0
print(discount_price(100, 0.5)) # 传 0.5,打 5 折:50.0
输出:
80.0
50.0
默认参数有三个讲究:
- 位置不能放错:默认参数必须写在所有普通参数的后面。
def f(a, b=1)没问题,def f(a=1, b)直接报语法错误SyntaxError: non-default argument follows default argument。 - 默认值别用列表/字典:
def f(items=[])这种写法有坑——列表会被所有调用共享,新手阶段先记住别这么写;需要可变默认值就写None,函数里再初始化。 - "没传"和"传了 None"是两回事:
greet("小明")和greet("小明", None)不一样,后者会把word真的变成None。
文档字符串:给函数写说明书
代码是给人看的。函数多了以后,连自己都容易忘这个函数是干嘛的。def 下面第一行用三引号 """...""" 写的说明叫文档字符串(docstring),相当于挂在函数上的说明书:说明这个函数干什么、参数是什么、返回什么。三引号内容不执行,只是说明。
def add(a, b):
"""计算两个数的和。
参数:
a: 第一个数
b: 第二个数
返回:
两数之和
"""
return a + b
print(add(3, 5))
# 用 help() 查看函数的"说明书"
help(add)
输出:
8
Help on function add in module __main__:
add(a, b)
计算两个数的和。
参数:
a: 第一个数
b: 第二个数
返回:
两数之和
加文档字符串不是必须的,但养成习惯有三个好处:自己回头看得懂;让 AI 帮你改代码时它读得懂;后面学到 help() 可以直接查看。
写的时候注意三点:一两句话讲清楚"干嘛的"就行,别写成小作文;# 注释是给读代码的人看的解释,"""...""" 文档字符串是挂在函数上、能被 help() 查到的说明书,两者不是一回事;改完代码记得同步改说明,注释和代码对不上,比没有注释更坑。
随堂练习
-
练习一(比大小):写一个函数
get_max(a, b),返回两个数中较大的那个,调用 3 次验证结果。 提示:先用if判断谁大,再用return返回;调用时用变量接住再打印。 -
练习二(判断奇偶):写一个函数
is_even(n),判断一个数是不是偶数,是偶数返回True,否则返回False,调用验证。 提示:偶数的标志是n % 2 == 0;打印返回值看看是 True 还是 False。 -
练习三(改造计算器,必做):把第 2 周写过的"计算器"(加减乘除四则运算)改造成函数版:写
add(a, b)、sub(a, b)、mul(a, b)、div(a, b)四个函数,主程序只负责输入数字和调用函数打印结果。 提示:改造前先把旧代码找出来;主程序用if判断运算符,然后调用对应函数。 -
★ 进阶题(乘法表函数):写一个函数
multi_table(n),打印到 n 的九九乘法表(n=9就是完整的九九乘法表)。 提示:要用for嵌套循环;格式用f"{i}x{j}={i*j}";先写 9 乘 9,再把 9 换成参数n。
写不出来?让 AI 给思路
这两段提示词今天就能用上,卡住的时候直接复制过去——
场景:写函数卡壳、不知道返回值怎么设计
提示词:
我是 Python 初学者,今天学了函数。请帮我完成下面这个练习:
写一个函数 get_max(a, b),返回两个数中较大的那个。
请先给我思路(不要直接给完整代码),用大白话解释形参和实参
的区别,然后我再自己写,写不出来你再给代码。代码要简单、加注释。
场景:改造旧代码为函数版本
提示词:
我有一段计算器代码(加、减、乘、除),现在想改造成用函数
封装:add(a, b)、sub(a, b)、mul(a, b)、div(a, b),主程序只负责
输入和调用。请帮我看这个改造思路对不对,并告诉我哪些地方
容易写错。我是初学者,请用大白话解释。
常见报错速查
今天遇到的报错基本就这几类,对号入座:
| 报错信息 | 什么意思 | 怎么办 |
|---|---|---|
IndentationError: expected an indented block |
函数体没有缩进 | 函数体比 def 行多缩进 4 个空格 |
NameError: name 'greet' is not defined |
函数名拼错,或调用写在定义前面 | 检查拼写;把调用移到定义后面 |
TypeError: greet() missing 1 required positional argument: 'name' |
调用时少传了参数 | 数一数定义了几个参数,调用时传齐 |
TypeError: unsupported operand type(s) for +: 'int' and 'str' |
返回值类型不对(比如把字符串当数字加) | 检查传入的参数类型,用 int() / float() 转换 |
调用后结果一直是 None |
函数里没写 return |
需要结果就给函数加 return 值 |
SyntaxError: non-default argument follows default argument |
默认参数写在了普通参数前面 | 默认参数统一放函数参数列表最后 |
收个尾
今天的东西说穿了就一句话:函数 = 把重复代码打包起名字,参数让它灵活,return 让它能交结果。你的程序从"一条一条往下念的长卷纸"变成"一抽屉一个的收纳盒",每个功能一格,想用哪格拿哪格。
写代码前先问自己一句:这段逻辑会不会用到第二次?会,就写成函数——第 6 周的项目全靠这个习惯撑起来。明天学 Python 自带的"工具箱"——标准库,你会见识到怎么让程序"随机掷骰子、随机点名"(random)、算数学(math)、看时间(datetime)。有了函数的基础,标准库的用法基本是一看就懂。
临走前想一想:def greet(name, word="你好") 这种写法,为什么默认参数必须放在所有普通参数的后面?