1. 函数的基本概念
什么是函数?
函数是一段可重复使用的代码块,用于执行特定任务。它接受输入(参数),进行处理,并返回结果。
就类似于一个加工厂,你给它一些原材料,它帮你进行处理之后返回成品。
生活比喻:
-
计算器(函数):输入两个数和运算符(原料),内部计算(步骤),输出结果(成品)
-
洗衣机(函数):输入脏衣服和洗衣液(原料),内部清洗甩干(步骤),输出干净衣服(成品)
为什么使用函数?---解决重复劳动的问题
没有函数的问题 : 如果每次计算"两个数的和"都要写 a + b,每次打印"欢迎语"都要写 print("欢迎使用!"),代码会变得冗长、重复,改一处要改很多地方。
函数的好处:
-
复用性:一段代码写一次,多次调用,不用重复写
-
简洁性:用一个名字代替一堆代码,逻辑更清晰
-
可维护性:改功能只需要改函数内部,不用到处改
2. 基础语法、分类、参数与返回值
函数的基本语法
函数是封装可复用逻辑的"工具" ,用 def 关键字定义,核心是"定义逻辑 → 调用执行"。
1. 函数的定义:"造工具"的步骤
语法格式:
python
def 函数名(参数列表):
"""函数文档字符串(可选,说明功能)"""
函数体代码(缩进块)
return 返回值(可选)
-
def:标记"开始定义函数"的关键字 -
函数名:自定义名称(见名知意,如calculate_sum表示"计算和"),遵循变量命名规则 -
参数列表:函数的"输入原料",可包含 0 个或多个参数,多参数用逗号分隔 -
函数体:缩进的代码块,是函数具体执行的逻辑 -
return:"输出结果"的关键字,可返回任意数据;若无return,函数默认返回None
2. 函数的调用:"用工具"的过程
定义后,通过 函数名(实际参数) 触发执行。
示例 1:无参数、无返回值
python
def say_hello():
"""打印欢迎语"""
print("你好,欢迎学习Python函数!")
say_hello() # 调用:输出"你好,欢迎学习Python函数!"
say_hello() # 再次调用,重复执行逻辑
示例 2:有参数、有返回值
python
def calculate_sum(a, b):
"""计算两个数的和并返回结果"""
result = a + b
return result
sum1 = calculate_sum(3, 5)
print("3 + 5 =", sum1) # 输出:3 + 5 = 8
sum2 = calculate_sum(10, 20)
print("10 + 20 =", sum2) # 输出:10 + 20 = 30
函数的分类
根据参数、返回值、功能,函数可分为以下 4 类:
1. 无参数、无返回值的函数
-
特点:无需输入"原料",也不输出"成品",仅执行逻辑(如打印、修改全局变量)
-
示例:
python
def show_menu():
print("===== 菜单 =====")
print("1. 查看个人信息")
print("2. 修改密码")
print("3. 退出系统")
2. 无参数、有返回值的函数
-
特点:无需输入,但会输出结果
-
示例:
python
import random
def get_random_num():
"""返回1-100的随机整数"""
return random.randint(1, 100)
num = get_random_num()
print("随机数:", num) # 输出:随机数:xxx(1-100之间)
3. 有参数、无返回值的函数
-
特点:需要输入"原料",但不输出"成品"(常用于直接执行动作)
-
示例:
pythondef print_info(name, age): """打印个人信息""" print(f"姓名:{name},年龄:{age}岁") print_info("张三", 20) # 输入参数,执行打印,无返回值4. 有参数、有返回值的函数
pythondef calculate_discount(price, rate): """计算打折后价格""" final = price * rate return final original = 100 discounted = calculate_discount(original, 0.8) print(f"原价{original}元,折后{discounted}元") # 输出:原价100元,折后80.0元3. 函数的参数
1. 位置参数(必选参数)
-
特点:调用时,实际参数的顺序、数量必须与定义时完全匹配
-
示例:
pythondef describe_person(name, age, city): print(f"姓名:{name},年龄:{age}岁,城市:{city}") describe_person("李四", 25, "北京") # 正确:按位置一一对应 # describe_person("王五", 30) # 错误:缺少 city 参数2. 默认值参数
-
特点:定义时给参数设默认值;调用时,若不传递该参数则用默认值
-
注意:默认参数必须放在位置参数之后
-
示例:
pythondef describe_person(name, age, city="上海"): # city 有默认值 print(f"姓名:{name},年龄:{age}岁,城市:{city}") describe_person("李四", 25) # 用默认值 → 输出:姓名:李四,年龄:25岁,城市:上海 describe_person("王五", 30, "广州") # 覆盖默认值 → 输出:姓名:王五,年龄:30岁,城市:广州3. 关键字参数
-
特点:调用时通过
参数名=值传递,无需遵循位置顺序,但参数名必须与定义一致 -
示例:
pythondef describe_person(name, age, city): print(f"姓名:{name},年龄:{age}岁,城市:{city}") # 关键字参数调用,顺序可任意 describe_person(age=25, name="李四", city="北京") # 正确4. 函数的返回值
return是函数的"成品出口",用于将内部结果传递到外部。1. 返回单个值
可返回任意数据类型(int、float、str、列表、字典等)。
def get_max(num1, num2): """返回两个数中的较大值""" return num1 if num1 > num2 else num2 max_num = get_max(10, 20) print("较大的数:", max_num) # 输出:较大的数:202. 返回多个值
Python 中
return会将多个值打包成元组 ,调用时可自动解包为多个变量。def calculate_stats(numbers): """返回列表的总和与平均值""" total = sum(numbers) average = total / len(numbers) return total, average # 多返回值 → 元组打包 nums = [1, 2, 3, 4, 5] total, avg = calculate_stats(nums) # 元组解包 print(f"总和:{total},平均值:{avg}") # 输出:总和:15,平均值:3.03.
return的"终止"作用函数执行到
return时会立即终止,后续代码不再执行。def say_hi(): print("Hi!") result = say_hi() print(result) # 输出:Hi! → None(先打印 Hi!,再打印返回的 None)5. 全局变量和局部变量
在 Python 中,变量的"作用范围"(即哪里能访问到这个变量)是区分全局变量和局部变量的核心。简单说:全局变量是"大家都能用"的变量,局部变量是"只有特定小范围能用"的变量
全局变量
定义 :在函数外部 定义的变量,就是全局变量。它的作用域(能被访问的范围)是整个程序(包括所有函数内部,只要没被局部变量覆盖)。
在函数外部定义全局变量
global_num = 100 # 全局变量:整个程序都能访问
def print_global():
# 函数内部可以直接访问全局变量
print("函数内访问全局变量:", global_num)def use_global():
# 函数内部可以使用全局变量进行计算
result = global_num * 2
print("用全局变量计算的结果:", result)调用函数
print_global() # 输出:函数内访问全局变量:100
use_global() # 输出:用全局变量计算的结果:200函数外部也能直接访问
print("函数外访问全局变量:", global_num) # 输出:函数外访问全局变量:100
局部变量
定义 :在函数内部 定义的变量,就是局部变量。它的作用域仅限当前函数内部,函数外部或其他函数都无法访问。
def create_local():
# 在函数内部定义局部变量
local_str = "我是局部变量" # 局部变量:仅在create_local()内可用
print("函数内访问局部变量:", local_str)
create_local() # 输出:函数内访问局部变量:我是局部变量
# 尝试在函数外部访问局部变量 → 报错!
# print("函数外访问局部变量:", local_str) # 报错:NameError: name 'local_str' is not defined
函数的执行过程
当调用一个函数时,系统会在 栈内存 中为该函数单独分配一块临时内存区域。这块区域专门用来存储:
函数的参数
函数内部定义的局部变量
函数执行的中间状态
此时,局部变量就"存放在这块临时区域里",只能在函数内部被访问(相当于"临时房间里的物品,只有房间内的操作能触及")
函数执行完:"出栈",局部变量的"临时房间"被销毁
当函数执行完毕(比如遇到
return或函数体结束),栈会执行 "出栈"操作 ------回收并销毁该函数的临时内存区域。这意味着:存储局部变量的内存空间被系统释放,局部变量彻底"消失"了。
通俗理解:
-
调用函数 → 租一个临时仓库(栈中分配临时内存),把局部变量当"货物"存在仓库里
-
函数执行完 → 退租仓库(栈回收内存),仓库被销毁,里面的"货物"(局部变量)也跟着消失,外部自然找不到
特殊情况:同名变量与 global 关键字
全局变量与局部变量同名
num = 100 # 全局变量
def test_same_name():
num = 200 # 局部变量(和全局变量同名)
print("函数内的num(局部):", num) # 优先用局部变量 → 200
test_same_name()
print("函数外的num(全局):", num) # 全局变量没变 → 100
global 关键字
默认情况下,函数内只能"访问"全局变量,不能直接"修改"它(如果直接赋值,会被当成定义局部变量)。如果要修改全局变量,必须在函数内用 global 关键字声明。
count = 0 # 全局变量
def correct_modify():
global count # 声明:我要修改的是全局变量count
count = 1 # 现在才是真正修改全局变量
print("函数内的count(全局):", count) # 输出:1
correct_modify()
print("函数外的count(全局):", count) # 全局变量已被修改 → 1
## 6. 值传递和引用传递
Python 中所有数据都是**对象**,变量只是"指向对象的引用(标签)"。当变量作为参数传给函数时,传递的是「对象引用的副本」------但后续操作是否影响原对象,取决于对象本身是"不可变"还是"可变"
### 不可变对象(如 `int`、`str`、`tuple`):类似"值传递",修改不影响原对象
不可变对象的特点:**创建后值无法修改**,任何"修改"操作都会生成**新的对象**(而非改变原对象)。
def modify_num(num):
num = num + 10 # 整数是不可变对象,此操作会生成新整数(15)
print("函数内的 num:", num)
a = 5
modify_num(a)
print("函数外的 a:", a) # 输出:函数外的 a: 5
解释:
a 是整数(不可变对象),传递给函数时,num 拿到的是「a 指向的整数 5 的引用副本」。
执行 num = num + 10 时,因整数不可变,会创建新的整数对象 15,并让 num 指向它(原对象 5 不受影响)。
因此,函数外的 a 仍指向原对象 5,值不变。
可变对象(如 list、dict、set):类似"引用传递",修改会影响原对象
可变对象的特点:可以在原地修改内容(不会生成新对象,直接修改原对象的内部数据)。
殊情况:对可变对象"重新赋值",不影响原对象
若在函数内对可变对象的参数重新赋值(让参数指向新对象),则不会影响外部原对象------因为这是让「引用副本」指向了新对象,而非修改原对象。
def reassign_list(lst):
lst = [10, 20, 30] # 让 lst 指向新列表(原列表不受影响)
print("函数内的 lst:", lst)
my_list = [1, 2, 3]
reassign_list(my_list)
print("函数外的 my_list:", my_list) # 输出:函数外的 my_list: [1, 2, 3]
总结:Python 的参数传递规则
-
传递的是 "对象引用的副本",而非"值的副本"或"对象本身的引用"
-
若对象是不可变 的(
int、str、tuple等):函数内"修改"会生成新对象,原对象不受影响 → 行为类似"值传递" -
若对象是可变 的(
list、dict、set等):函数内"原地修改内容"会影响原对象 → 行为类似"引用传递";但"重新赋值参数"不会影响原对象
7. 函数的递归
在 Python 中,递归(Recursion) 指的是函数自己调用自己 的编程方式。它的核心思想是:把一个复杂问题分解为和原问题"结构相同但规模更小"的子问题,直到子问题小到可以直接解决(终止条件),再通过子问题的解反推原问题的解。
递归的两个核心思想
递归函数必须包含两个部分,否则会陷入"无限递归"(类似死循环):
-
终止条件(Base Case):当问题小到可以直接解决时,返回一个明确的结果,不再继续调用自己
-
递归步骤(Recursive Case):将原问题分解为"规模更小的同类子问题",并通过调用自身解决子问题,最终用子问题的解构建原问题的解
递归的优缺点
优点:
-
代码简洁,逻辑清晰
-
适合解决分治、树形结构等问题
缺点:
-
效率较低:每次递归调用都会在内存栈中开辟新空间(存储函数参数、返回地址等),可能导致额外的时间和空间开销
-
可能栈溢出 :Python 对递归深度有默认限制(约 1000 层),超过会抛出
RecursionError -
调试困难:多层递归的调用栈复杂,出错时难以追踪问题
递归练习题
-
用递归计算斐波那契数列的第
n项(斐波那契定义:fib(0)=0,fib(1)=1,fib(n)=fib(n-1)+fib(n-2)) -
用递归计算
1 + 2 + 3 + ... + n的和(提示:sum(n) = n + sum(n-1),终止条件sum(1)=1) -
用递归实现字符串反转(如输入
"abc",输出"cba",提示:reverse(s) = s[-1] + reverse(s[:-1]),终止条件reverse("") = "")
8. 匿名函数
匿名函数 指的是"没有名字的函数",它通过 lambda 关键字定义,因此也被称为 lambda 函数 。匿名函数的核心作用是快速定义简单逻辑的函数,通常用于"临时需要一个函数"的场景(比如作为其他函数的参数)
基本语法
lambda 参数列表: 表达式
-
参数列表:函数的输入参数,可以有 0 个或多个参数
-
表达式 :函数的执行逻辑,必须是单个表达式 (不能包含循环、多行代码等复杂逻辑),表达式的结果会自动作为函数的返回值(无需写
return)
使用示例
1. 定义匿名函数并赋值给变量
# 定义匿名函数并赋值给变量
add = lambda a, b: a + b
# 通过变量调用
print(add(3, 5)) # 输出:8(等价于普通函数的调用)
2. 作为其他函数的参数
people = [
{"name": "张三", "age": 20},
{"name": "李四", "age": 18},
{"name": "王五", "age": 22}
]
# 按年龄升序排序,key=lambda x: x["age"] 表示"以字典的'age'值作为排序依据"
sorted_people = sorted(people, key=lambda x: x["age"])
print(sorted_people)
# 输出:[{'name': '李四', 'age': 18}, {'name': '张三', 'age': 20}, {'name': '王五', 'age': 22}]
9. 偏函数
偏函数(Partial Function) 是 functools 模块提供的一个工具,它的核心作用是:固定原函数的部分参数(提前给某些参数赋值),生成一个新的函数。这样在调用新函数时,只需传递剩下的参数,无需重复传递那些固定的参数,让代码更简洁。
使用场景
例如,Python 内置的 int() 函数可以将字符串转换为整数,它有一个可选参数 base(默认值为 10),用于指定字符串的进制:
-
int("10")→ 默认按 10 进制转换,结果为 10 -
int("10", base=2)→ 按 2 进制转换,结果为 2 -
int("10", base=8)→ 按 8 进制转换,结果为 8
如果你的场景中频繁需要将二进制字符串转整数 (即 base=2 固定),每次调用都写 int(s, base=2) 会很重复。这时,偏函数就能帮你"固定 base=2",生成一个新的"二进制转换专用函数"。
基本语法
from functools import partial
新函数 = partial(原函数, 固定参数1=值1, 固定参数2=值2, ...)