引言:为什么函数是Python编程的灵魂?
在编程的世界里,有一句话广为流传:“Don‘t Repeat Yourself”(不要重复自己)。这不仅仅是编程界的金科玉律,更是衡量代码质量的试金石。
还记得你第一次写Python代码时,为了打印几个星星图案,不得不反复编写相同的循环代码吗?今天,我们就来深入探讨Python中最核心、最实用的概念——函数。掌握函数,你的编程水平将迈上一个全新的台阶!
一、函数是什么?为什么要用函数?
1.1 函数的本质
想象一下,你正在厨房做一道复杂的菜肴。如果每次做菜都要从切菜、调味到烹饪全部重新开始,那将是多么低效的事情。聪明的厨师会将常用的步骤封装成“标准流程”,需要时直接执行即可。
Python函数正是如此:它是一段带名字的代码块,专门用于完成特定的任务,并且可以重复使用。
1.2 一个真实的痛点场景
假设我们需要在控制台打印一个2行3列的星形图案:
python
row = 2 while row > 0: print("*" * 3) row -= 1如果程序需要在多个地方打印这个图案,按照传统方式,你可能会复制粘贴这段代码。这就产生了代码冗余——同样的逻辑散落在程序的各个角落,一旦需要修改(比如改成3行4列),你就需要在所有地方逐一修改,既耗时又容易出错。
这就是函数诞生的理由!
二、函数的定义与调用
2.1 函数的基本结构
Python定义函数使用def关键字,基本语法如下:
python
def 函数名(参数列表): """函数说明文档(可选)""" 函数体 return 返回值(可选)
让我们用这个语法改造上面的打印星形案例:
python
def printStar(): """打印2行3列的星形图案""" row = 2 while row > 0: print("*" * 3) row -= 1 # 调用函数 printStar() print("-" * 20) printStar() # 再次调用,代码复用!输出结果:
text
*** *** -------------------- *** ***
看到了吗?同样的功能,现在只需要调用一次函数名就能完成,代码变得简洁而优雅。
2.2 函数名的命名规范
函数名应该遵循以下规范:
动词开头:函数执行的是动作,所以应该用动词命名,如
printStar、getUserInfo小写字母开头:第一个单词全部小写
多单词用下划线分隔或驼峰命名:Python社区推荐使用下划线分隔,如
print_star
三、函数的参数——让函数变得灵活
3.1 参数的必要性
回到刚才的例子,如果我们现在想打印1行4列的星形图案怎么办?重新写一个函数吗?那又产生了冗余!
聪明的做法:让函数接收参数,由调用者决定打印的行数和列数。
python
def printStar(row, col): """打印指定行数列数的星形图案""" while row > 0: print("*" * col) row -= 1 # 调用时传入不同的参数 printStar(2, 3) # 打印2行3列 print("-" * 20) printStar(1, 4) # 打印1行4列3.2 形参与实参
这里需要明确两个重要概念:
形参(形式参数):定义函数时括号内的参数,如
row和col,它们只是占位符,不占用实际内存实参(实际参数):调用函数时传递的具体值,如
2, 3和1, 4
当函数被调用时,实参的值会传递给形参,函数内部就可以使用这些值执行操作。
3.3 Python参数传递的底层原理
这是很多初学者容易混淆的地方。Python中的参数传递究竟是值传递还是引用传递?
答案是:既不是纯粹的值传递,也不是纯粹的引用传递,而是“传对象引用”。
Python中,变量实际上是对象的引用(可以理解为贴在对象上的标签)。当我们传递参数时,传递的是引用的副本。
不可变对象(数字、字符串、元组)
python
def changeInt(a): print("函数内改变前a的地址:", id(a)) a = 10 print("函数内改变后a的地址:", id(a)) b = 2 print("函数外b的地址:", id(b)) changeInt(b) print("函数外b的值:", b) # 输出:2,并未改变输出结果:
text
函数外b的地址: 140711474555352 函数内改变前a的地址: 140711474555352 函数内改变后a的地址: 140711474555608 函数外b的值: 2
分析:a = 10实际上是让a指向了一个新的整数对象10,而b仍然指向原来的2。因此外部变量不会被修改。
可变对象(列表、字典、集合)
python
def changeList(myList): myList[1] = 50 print("函数内列表:", myList) print("函数内列表地址:", id(myList)) mlist = [1, 2, 3] print("函数外列表地址:", id(mlist)) changeList(mlist) print("函数外列表:", mlist) # 输出:[1, 50, 3],已被修改输出结果:
text
函数外列表地址: 1546427570560 函数内列表: [1, 50, 3] 函数内列表地址: 1546427570560 函数外列表: [1, 50, 3]
分析:myList[1] = 50修改的是列表内部的元素,并没有改变列表对象的引用,所以外部列表也被修改了。
关键结论:在函数内修改参数时,如果是可变对象,可以直接修改;如果是不可变对象,需要重新赋值,但这不会影响外部变量。
3.4 四种参数类型
1)必须参数(位置参数)
调用函数时必须按照定义顺序传入相同数量的参数:
python
def func(a, b, c): print(a, b, c) func(1, 2, 3) # 正确:1传给a,2传给b,3传给c # func(1, 2) # 错误:缺少参数
2)关键字参数
调用时指定参数名,可以无视顺序:
python
def printInfo(name, age): print(f"姓名:{name}, 年龄:{age}") # 以下两种调用方式等效 printInfo(name="张三", age=18) printInfo(age=18, name="张三")3)默认值参数
为参数提供默认值,调用时可省略:
python
def printInfo(name, age=20): print(f"姓名:{name}, 年龄:{age}") printInfo("张三") # 年龄使用默认值20 printInfo("李四", 30) # 年龄使用传入的30 printInfo(age=40, name="王五") # 关键字参数重要规则:带默认值的参数必须放在不带默认值参数的后面,否则会报语法错误。
python
# 错误示例 def wrong(name="张三", age): # SyntaxError pass # 正确示例 def correct(age, name="张三"): pass
4)不定长参数
当不确定需要传入多少个参数时使用:
单星号*args:接收任意多个位置参数,打包成元组
python
def printInfo(num, *vartuple): print("固定参数:", num) print("不定长参数:", vartuple) printInfo(70, 60, 50) # 输出: # 固定参数: 70 # 不定长参数: (60, 50) # 如果不定长参数后面还有参数,必须用关键字传参 def printInfo2(num1, *vartuple, num): print(num1, vartuple, num) printInfo2(10, 20, num=40) # 正确双星号**kwargs:接收任意多个关键字参数,打包成字典
python
def printInfo(num, **vardict): print("固定参数:", num) print("关键字参数:", vardict) printInfo(10, key1=20, key2=30) # 输出: # 固定参数: 10 # 关键字参数: {'key1': 20, 'key2': 30}3.5 解包传参
如果已经有一个列表或字典,想将其作为参数传入,可以使用*和**解包:
python
def func(a, b, c): return a + b + c # 解包列表 tuple11 = (1, 2, 3) print(func(*tuple11)) # 输出:6 # 解包字典(字典的键必须和参数名一致) dict1 = {"a": 1, "b": 2, "c": 3} print(func(**dict1)) # 输出:63.6 强制参数传递方式
Python 3.8+引入了更精确的参数传递控制:
/前的参数:必须使用位置传参*后的参数:必须使用关键字传参
python
def f(a, b, /, c, d, *, e, f): print(a, b, c, d, e, f) f(1, 2, 3, d=4, e=5, f=6) # 正确 # f(1, 2, c=3, d=4, e=5, f=6) # 也正确 # f(1, b=2, 3, d=4, e=5, f=6) # 错误:b必须在/之前位置传参
四、函数的返回值
4.1 返回值的作用
函数执行完成后,有时需要将结果返回给调用者,这就是返回值的作用。
python
def add(num1, num2): """求两个数的和""" return num1 + num2 result = add(10, 20) print("两个数的和为:", result) # 输出:304.2 return的多种形式
带表达式的return:返回计算结果
不带表达式的return:返回None
没有return语句:函数执行完也会返回None
返回多个值:多个值会被打包成元组
python
def f(a, b, c): return a, b, c, [a, b, c] print(f(1, 2, 3)) # 输出:(1, 2, 3, [1, 2, 3])
重要:return语句会立即结束函数执行,return后的代码不会执行。
五、变量作用域
5.1 作用域的概念
Python有4种作用域,按查找顺序为:
L (Local)→E (Enclosing)→G (Global)→B (Built-in)
python
a = int(2.9) # 内建作用域 b = 0 # 全局作用域 def outer(): c = 1 # 嵌套作用域 def inner(): d = 2 # 局部作用域 print(d, c, b, a) # 从内到外查找变量 return inner in_func = outer() in_func() # 输出:2 1 0 2
5.2 全局变量与局部变量
python
sum = 0 # 全局变量 def add(num1, num2): sum = num1 + num2 # 局部变量 print("函数内局部变量:", sum, id(sum)) return sum add(10, 20) print("函数外全局变量:", sum, id(sum)) # 输出: # 函数内局部变量: 30 140711474555608 # 函数外全局变量: 0 140711474555352核心原则:在函数内部修改全局变量,默认会被当作局部变量处理。
5.3 global关键字
如果想在函数内修改全局变量,必须使用global声明:
python
var1 = 100 def function_a(): global var1 var1 = 200 print("函数内var1:", var1) print(var1) # 100 function_a() # 200 print(var1) # 200特殊情况:可变类型的全局变量,不声明global也可以修改其内部元素:
python
list1 = [1, 2, 3] def function_a(): list1[0] = -1000 # 修改列表元素 print("list1:", list1) print(list1) # [1, 2, 3] function_a() # [-1000, 2, 3] print(list1) # [-1000, 2, 3]5.4 nonlocal关键字
用于修改嵌套作用域中的变量(非全局):
python
def function_outer(): var1 = 1 print("外层var1:", var1) # 1 def function_inner(): nonlocal var1 var1 = 200 function_inner() print("修改后var1:", var1) # 200 function_outer()六、递归函数
6.1 什么是递归?
递归是一种编程思想:函数调用自身,将一个大问题分解为若干个小问题,直到达到一个可立即解决的简单情况。
6.2 阶乘的递归实现
python
def get_factorial(n): """计算n的阶乘""" return n * get_factorial(n - 1) if n > 1 else 1 print(get_factorial(5)) # 输出:120
6.3 递归的执行流程
当调用get_factorial(5)时,执行流程如下:
text
get_factorial(5) = 5 * get_factorial(4) get_factorial(4) = 4 * get_factorial(3) get_factorial(3) = 3 * get_factorial(2) get_factorial(2) = 2 * get_factorial(1) get_factorial(1) = 1
然后层层返回:
text
get_factorial(2) = 2 * 1 = 2 get_factorial(3) = 3 * 2 = 6 get_factorial(4) = 4 * 6 = 24 get_factorial(5) = 5 * 24 = 120
递归的两个关键要素:
递归规律:找出问题分解的规律
结束条件:必须有明确的终止条件,否则会无限递归导致栈溢出
七、匿名函数(Lambda)
7.1 Lambda语法
Lambda是Python中定义匿名函数的方式,语法简洁:
python
lambda 参数列表: 表达式
等价于:
python
def 函数名(参数列表): return 表达式
7.2 基本使用
python
# 普通函数 def add(a, b): return a + b # Lambda表达式 add_lambda = lambda a, b: a + b print(add(1, 2)) # 3 print(add_lambda(1, 2)) # 3
7.3 Lambda与高阶函数
Lambda经常与内置高阶函数配合使用:
1) sorted() - 排序
python
students = [ {"name": "张三", "age": 36}, {"name": "李四", "age": 14}, {"name": "王五", "age": 27} ] # 按年龄排序 sorted_students = sorted(students, key=lambda x: x["age"]) print(sorted_students) # 输出:[{'name': '李四', 'age': 14}, {'name': '王五', 'age': 27}, {'name': '张三', 'age': 36}]2) map() - 映射
python
# 对列表每个元素平方 result = map(lambda x: x * x, [0, 1, 3, 7, 9]) print(list(result)) # [0, 1, 9, 49, 81]
3) filter() - 过滤
python
# 过滤出非负数 result = filter(lambda x: x >= 0, [-0, -1, -3, 7, 9]) print(list(result)) # [0, 7, 9]
4) reduce() - 累积
python
from functools import reduce # 计算1*2*3*4*5 result = reduce(lambda x, y: x * y, [1, 2, 3, 4, 5]) print(result) # 120
八、函数的最佳实践
8.1 编写函数说明文档
好的函数应该包含清晰的文档字符串:
python
def adult(age=18): """根据年龄判断是否成年 参数: age: 年龄,默认18岁 返回: 字符串,'成年'或'未成年' """ return "未成年" if age < 18 else "成年" # 查看函数文档 help(adult)
8.2 防止函数修改列表
如果不想让函数修改原始列表,可以传递深拷贝:
python
import copy def multiply2(var1): var1[3].append(400) print("函数内处理后:", var1) list1 = [1, 2, 3, [100, 200, 300]] print("函数外处理前:", list1) multiply2(copy.deepcopy(list1)) print("函数外处理后:", list1) # 原始列表未被修改8.3 函数注释(Type Hints)
Python 3.x支持函数注释,提高代码可读性:
python
def dog(name: str, age: (1, 99), species: '狗狗的品种') -> tuple: """带注释的函数""" return (name, age, species) print(dog.__annotations__) # 输出:{'name': <class 'str'>, 'age': (1, 99), 'species': '狗狗的品种', 'return': <class 'tuple'>}九、总结
通过本文的深入学习,我们系统地掌握了Python函数的核心知识:
核心要点回顾
函数的意义:代码复用、提高可维护性、降低复杂度
定义与调用:
使用
def定义函数函数先定义后调用
函数名应使用动词开头
参数系统:
必须参数、关键字参数、默认值参数、不定长参数
理解形参和实参的区别
掌握Python的参数传递机制(传对象引用)
返回值:
return结束函数并返回值可以返回多个值(打包成元组)
无
return或空return返回None
变量作用域:
局部变量、全局变量、嵌套作用域
使用
global修改全局变量使用
nonlocal修改嵌套变量
递归:
函数调用自身
必须包含结束条件
适用于分治思想的问题
Lambda表达式:
简洁的匿名函数
常用于高阶函数(map、filter、reduce、sorted)
最佳实践:
编写清晰的文档字符串
合理使用类型注释
注意可变对象的副作用
进阶建议
掌握函数之后,你的Python编程能力将进入一个新的阶段。建议你:
多练习:将日常的重复代码封装成函数
深入理解作用域:这是避免bug的关键
善用高阶函数:让代码更简洁优雅
编写单元测试:确保函数在各种情况下都能正确运行
函数是Python程序的基本组成单元,就像建筑的砖块。打好基础,才能构建出稳固而优雅的程序架构。希望这篇文章能帮助你真正理解并灵活运用Python函数,让你的编程之路越走越远!
如果你觉得这篇文章对你有帮助,欢迎点赞、收藏、转发,让更多人受益!