| Day 8 | 函数入门:def、参数、返回值与作用域 |

1 函数是什么 —— 把「复制粘贴」升级成「命名复用」

是什么?为什么学?

函数(function)是给一段代码起个名字,以后通过名字反复调用它的语法结构。你有没有发现,前面写的代码里有些功能反复出现:求和、判断奇偶、算平均……每次都要重新写一遍。函数就是「给一段代码起个名字,以后想用就叫名字」——一次写好,到处使用。它是编程里最重要的「复用」工具,也是拆解复杂任务的唯一方式。

底层原理

def 定义函数时,Python 并不会执行函数体,而是把「函数体代码」和「参数信息」打包成一个函数对象,绑定到函数名上;只有调用(写 函数名())时,Python 才会新建一个独立的「执行空间」(栈帧),把函数体从头执行一遍,执行完销毁这个空间。这就是「定义不执行、调用才执行」的底层原因,也是函数内变量与函数外变量互不干扰的根源。

术语小词典:栈帧(stack frame)就是「函数运行时临时搭起的工作台」,函数跑完工作台就拆掉,上面的局部数据也随之消失。

生活类比

函数像菜谱:把菜谱写进笔记本(def 定义)不等于菜做好了;照着菜谱做一遍(调用)才出菜。同一本菜谱可以被不同的人、不同的时间反复使用——这就是「一次编写、到处调用」。

注意事项总结

1 手写三遍 vs 函数写一遍调三遍

def say_hello():
    print("你好!")

say_hello()
say_hello()
say_hello()

运行结果:

你好!
你好!
你好!

函数体只写一份,想调用多少次都行——这就是复用的意义。

2 函数体可以有多行

def print_plan():
    print("今天的学习计划:")
    print("1. 复习列表")
    print("2. 学函数")

print_plan()

3 为什么用函数:一是复用(同一段逻辑只写一次);二是分工(一个大问题拆成几个小函数,每个只管一件事);三是好改(要改逻辑只改一个地方)。

易错点

  • 错误写法:def say_hello() 行尾忘了冒号 → 问题:报 SyntaxError: invalid syntax → 正确写法:def 行尾必须有冒号:def say_hello():
  • 错误写法:调用写在定义之前(say_hello() 放第一行) → 问题:报 NameError,因为 def 还没执行、名字还不存在 → 正确写法:先定义、后调用。
  • 错误写法:f = say_hello 误写成 f = say_hello() → 问题:前者拿到函数本身;后者执行函数并把返回值(None)赋给 f → 正确写法:想传函数本身不写括号;想拿结果才写括号。

记忆口诀:def 登记不动手,括号调用才执行;名字绑定函数体,一次编写处处用。

2 参数 —— 实参按位置「对号入座」

是什么?为什么学?

参数(parameter)是函数的「入口」:定义时括号里写的名字是形参(占位符),调用时传进去的值是实参(真数据)。没有参数的函数只能做固定的事,有了参数,同一个函数才能处理不同的数据——这是函数通用的关键。

底层原理

调用 greet("小明") 时,Python 按位置一一对应:第 1 个实参给第 1 个形参,第 2 个实参给第 2 个形参。形参在函数执行时成为「局部名字」,指向实参传入的对象。注意 Python 传的是「对象的引用」——形参和实参指向同一个对象(好比把同一张快递单号抄了一份递给函数,单号指向同一个包裹)。

生活类比

参数像点餐单:菜单上写「汉堡(夹什么、加不加辣)」是形参;你下单写「汉堡夹牛肉、加辣」是实参。后厨按你填的单子做——每一项都要对上,写错位置、漏写一项,菜就做不对。

注意事项总结

1 多个参数按顺序一一对应

def describe(name, age):
    print(name, "今年", age, "岁")

describe("小明", 18)
describe("小红", 17)

运行结果:

小明 今年 18 岁
小红 今年 17 岁

调用时实参按位置配对传给形参:"小明" 给 name、18 给 age。

2 形参和全局变量同名也互不干扰

def show(x):
    print("函数内 x =", x)

x = 100          # 全局的 x
show(7)          # 传入的 7 只给函数里的 x
print("函数外 x =", x)

运行结果:

函数内 x = 7
函数外 x = 100

函数内的 x 是形参(局部名字),和函数外的全局 x 只是同名不同物

3 实参个数和形参个数必须一致(错误示范)

def add(a, b):
    return a + b

# add(3)          # 报错!TypeError: add() missing 1 required positional argument: 'b'
# add(3, 5, 7)    # 报错!TypeError: add() takes 2 positional arguments but 3 were given

报错信息里会明确告诉你「缺了哪个参数」或「多了几个参数」——学会读报错信息,就能自己定位问题。

易错点

  • 错误写法:def greet(name, age) 调用写成 greet(18, "小明") → 问题:参数顺序反了,name 拿到 18,逻辑错乱且不报错 → 正确写法:实参顺序必须与形参定义顺序一致。
  • 错误写法:调用时少传一个参数 → 问题:报 TypeError: missing 1 required positional argument → 正确写法:检查调用处参数个数是否与定义一致。
  • 错误写法:形参名写错(def greet(nmae),函数体内用 name) → 问题:报 NameError,函数体内找不到这个名字 → 正确写法:形参名定义和使用保持一致。

记忆口诀:形参占位实参值,调用按位对号坐;个数顺序都对齐,函数才能跑得对。

3 return —— 函数与调用者的「交接棒」

是什么?为什么学?

return 是函数把结果交还给调用者的语句。print 只是把内容显示在屏幕上,看完就没了;return 把结果真正「递」出来,调用处能把它存进变量、继续参与计算。想让函数「有用」,十有八九要靠 return。

底层原理

函数调用会创建独立的执行空间,函数里的计算结果默认带不出来。return 的作用就是把一个值从函数内部「递交」回调用者的执行空间,然后立即结束函数。如果函数没有 return,Python 会自动补一个「返回 None」——None 是表示「什么都没有」的特殊值。另外 return a, b 实际上返回的是一个元组,可以一次性交回多个结果。

生活类比

食堂打饭窗口:print 是窗口上方的显示屏——大家都看得见,但你拿不到;return 是打饭阿姨从窗口递出来的餐盘——只有递到你手里(存进变量),你才能吃(继续使用)。没有 return 的函数就像只给你看了一眼菜,什么也没递出来。

注意事项总结

1 return 的结果可以存进变量继续用

def add(a, b):
    return a + b

result = add(3, 5)      # 把返回的 8 接住
print(result)           # 8
print(result * 10)      # 还能继续参与运算:80

运行结果:

8
80

对比:如果 add 里用的是 print 而不是 return,result 会拿到 None,第 6 行就会报错。

2 没有 return 的函数,调用结果是 None

def show():
    print("我在打印")

r = show()
print(r)          # None
print(r is None)  # True

运行结果:

我在打印
None
True

is None 是判断「是不是 None」的标准写法。调试时发现「函数结果用不了」,先检查函数里有没有写 return。

3 return 返回多个值(自动打包成元组)

def get_min_max(nums):
    return min(nums), max(nums)

a, b = get_min_max([3, 8, 1, 9])   # 一次接住两个结果
print("最小值:", a)
print("最大值:", b)

运行结果:

最小值: 1
最大值: 9

return min(nums), max(nums) 看起来返回两个值,实际返回一个元组 (1, 9)a, b = ...元组解包

4 遇到 return 函数立即结束

def check(score):
    if score < 0:
        return "分数不合法"   # 提前返回,后面的代码不执行
    return "分数: " + str(score)

print(check(-5))
print(check(85))

运行结果:

分数不合法
分数: 85

易错点

  • 错误写法:用 print 代替 return(def add(a, b): print(a + b)) → 问题:调用处拿到 None,结果无法参与计算 → 正确写法:需要结果就 return a + b
  • 错误写法:return a, b 但只用 x = get_min_max(...) 接收 → 问题:x 拿到的是元组 (a, b),不是两个独立变量 → 正确写法:用 a, b = get_min_max(...) 解包。
  • 错误写法:忘了写 return → 问题:函数「算完就丢」,调用处拿到 None → 正确写法:检查函数是否有返回值需求,需要就写 return。

记忆口诀:print 只展示,return 真递出;return 一到就收工,没写默认返 None。

4 默认参数与变量作用域 —— 两个经典坑提前避开

是什么?为什么学?

默认参数(default parameter)让某些参数「不传也有值」;作用域(scope)决定「变量在哪些地方可见」。这两个概念是新手写函数时踩坑最多的两个地方,今天一次讲透,后面能少掉很多头发。

底层原理

默认参数的值在 def 定义的那一刻只计算一次,之后每次调用共用同一个对象——如果默认值是列表、字典这类「可变对象」,多次调用会互相污染。作用域方面,Python 查找变量按 LEGB 顺序:局部(Local)→ 嵌套函数(Enclosing,Day 9 才接触)→ 全局(Global)→ 内置(Built-in)。函数里读取全局变量没问题;但函数里赋值count = count + 1)时,Python 会认为 count 是新的局部变量——想改全局必须显式声明 global

生活类比

默认参数像酒店房间的默认配置:你不特别要求,前台就按「大床房、无烟」安排;你提了要求,就按你的来。作用域像公司的储物柜:大厅的公共柜(全局)谁都能看;工位的私人抽屉(局部)只有你自己能用——想动公共柜里的东西(改全局变量),得先和管理员打招呼(写 global)。

注意事项总结

1 默认参数——不传用默认,传了用你的

def greet(name, greeting="你好"):
    print(greeting, name)

greet("小明")              # 不传 greeting,用默认「你好」
greet("小红", "早上好")     # 传了就用传入的

运行结果:

你好 小明
早上好 小红

有默认值的参数必须写在没有默认值的参数后面def greet(name, greeting="你好") 对;def greet(name="小明", greeting) 会直接报语法错误)。

2 可变默认值陷阱(错误示范)

def add_item(item, bag=[]):
    bag.append(item)
    return bag

print(add_item("苹果"))    # 第一次:['苹果']
print(add_item("香蕉"))    # 期望 ['香蕉'],实际 ['苹果', '香蕉']!

运行结果:

['苹果']
['苹果', '香蕉']

bag=[] 这个列表在 def 定义时只创建一次,所有没传 bag 的调用共用它。正确做法是默认值用 None,函数里再创建新列表:

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

print(add_item("苹果"))    # ['苹果']
print(add_item("香蕉"))    # ['香蕉']

规则:默认值只用不可变类型(数字、字符串、None),绝不用列表/字典。

3 作用域——函数里读全局、写局部(同名遮蔽)

x = 10              # 全局 x

def show():
    x = 20          # 这是函数里的局部 x,和全局 x 没关系
    print("函数内 x =", x)

show()              # 函数内 x = 20
print("函数外 x =", x)   # 全局 x 没被改动,还是 10

运行结果:

函数内 x = 20
函数外 x = 10

函数里 x = 20 创建的是局部 x,只是「遮蔽」了全局 x 的名字,并不会修改全局变量。想修改全局变量必须加 global 声明,但最佳实践是尽量别用 global——用参数传进去、用 return 传出来,函数才独立、好维护。

易错点

  • 错误写法:def f(a=1, b): 默认参数写在普通参数前面 → 问题:直接报 SyntaxError: non-default argument follows default argument → 正确写法:默认参数一律写在普通参数后面。
  • 错误写法:def f(lst=[]) 且函数里修改 lst → 问题:多次调用共用同一个列表,数据互相污染 → 正确写法:默认值写 None,函数里 if lst is None: lst = []
  • 错误写法:函数里写 count = count + 1 想改全局 count → 问题:报 UnboundLocalError(或行为异常) → 正确写法:函数内声明 global count,或改用参数 + return。
  • 错误写法:以为函数里的局部变量在函数外还能用 → 问题:报 NameError,局部变量随函数结束而销毁 → 正确写法:需要结果就用 return 传出来。

记忆口诀:默认参数放后面,可变默认用 None;全局修改加 global,能不用就尽量不。

5 动手实践:数学工具函数库

把常用的数学运算封装成一个个小函数,放进一个文件,这就是「函数库」的雏形。

# 数学工具函数库
import math


def add(a, b):
    """两数相加"""
    return a + b


def multiply(a, b):
    """两数相乘"""
    return a * b


def average(nums):
    """求列表的平均值(空列表返回 0)"""
    if len(nums) == 0:
        return 0
    return sum(nums) / len(nums)


def factorial(n):
    """计算 n 的阶乘(1*2*3*...*n)"""
    result = 1
    for i in range(2, n + 1):
        result = result * i
    return result


def circle_area(radius):
    """计算圆的面积(πr²)"""
    return math.pi * radius * radius


# 调用测试
print(add(3, 4))                    # 7
print(multiply(3, 4))               # 12
print(average([85, 92, 78]))        # 85.0
print(average([]))                  # 0
print(factorial(5))                 # 120
print(round(circle_area(2), 2))     # 12.57(保留两位小数)

完成标准:每个函数都能独立调用、输出符合预期;再自己给函数库加一个「求列表最大值」的函数(提示:函数内可以用循环,也可以直接用 max)。

小知识:calc(price, rate=0.1) 里用 round(price * (1 + rate), 2) 保留两位小数——计算机用二进制存小数,0.1 这种小数无法精确表示,所以 100 * 1.1 的真实结果是 110.00000000000001,用 round(..., 2) 结果才和日常计算一致,写金额计算都建议这么做。

今日总结

函数是什么:def 定义、函数名() 调用,定义不执行、调用才执行;函数本身也是「值」,可以赋值给变量 函数的底层:def 把函数体打包成对象,调用时才创建独立执行空间(栈帧) 参数:形参是占位符、实参是真实数据,按位置一一对应,个数必须一致 返回值:print 只显示、return 真递出;return 一执行函数立即结束;没写 return 返回 None;return a, b 返回元组可解包 默认参数:写在普通参数后面,不传有默认值;可变默认值(列表/字典)必须用 None 代替 作用域:局部变量函数外访问不到;函数内读取全局没问题,修改全局要 global,最好用参数 + return 代替 完成数学工具函数库,至少含 5 个函数并通过自测

报错原因修复
SyntaxError: invalid syntaxdef 行尾忘了冒号,或函数名/参数名有非法字符检查 def 函数名(参数): 的冒号和命名
IndentationError: expected an indented block函数体忘了缩进def 下一行统一缩进 4 个空格
NameError: name 'x' is not defined在函数外访问函数内的局部变量需要结果就用 return 传出来,或把变量定义在函数外
TypeError: add() missing 1 required positional argument: 'b'调用时少传了一个参数检查调用是否与定义参数个数一致
UnboundLocalError: local variable 'count' referenced before assignment函数里给全局变量赋值但没声明 global在函数内先写 global 变量名
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇