**摘要:**函数是Python编程中最核心的概念之一。本文从函数的定义与必要性出发,系统梳理了内置函数、模块函数和自定义函数三大分类,深入讲解了形参与实参、位置传参、关键字传参、传参方式限制、默认参数以及 *args 和 **kwargs 不定长参数等完整参数体系,并通过打包与解包、返回值机制、局部变量与全局变量作用域、函数嵌套与递归调用等内容,帮助读者构建从基础到进阶的函数知识框架。
目录
[1. 什么是函数?](#1. 什么是函数?)
[2. 为什么需要函数?](#2. 为什么需要函数?)
[3. 函数的分类](#3. 函数的分类)
[3.1 内置函数](#3.1 内置函数)
[3.2 模块函数](#3.2 模块函数)
[3.3 自定义函数](#3.3 自定义函数)
[4. 函数参数详解](#4. 函数参数详解)
[4.1 形参与实参](#4.1 形参与实参)
[4.2 位置传参](#4.2 位置传参)
[4.3 关键字传参](#4.3 关键字传参)
[4.4 限制传参方式](#4.4 限制传参方式)
[4.5 默认参数](#4.5 默认参数)
[4.6 不定长参数(*args 和 **kwargs)](#4.6 不定长参数(*args 和 **kwargs))
[5. 参数的打包与解包](#5. 参数的打包与解包)
[5.1 序列解包(* 用于列表/元组)](#5.1 序列解包(* 用于列表/元组))
[5.2 字典解包(** 用于字典)](#5.2 字典解包(** 用于字典))
[5.3 打包与解包结合使用](#5.3 打包与解包结合使用)
[6. 函数的返回值](#6. 函数的返回值)
[6.1 基本用法](#6.1 基本用法)
[6.2 返回多个值](#6.2 返回多个值)
[6.3 没有return语句的情况](#6.3 没有return语句的情况)
[6.4 return终止函数执行](#6.4 return终止函数执行)
[7. 局部变量与全局变量](#7. 局部变量与全局变量)
[7.1 局部变量](#7.1 局部变量)
[7.2 全局变量](#7.2 全局变量)
[7.3 嵌套函数中的变量:nonlocal](#7.3 嵌套函数中的变量:nonlocal)
[8. 函数中调用函数](#8. 函数中调用函数)
[8.1 普通嵌套调用](#8.1 普通嵌套调用)
[8.2 递归调用](#8.2 递归调用)
[8.3 在函数内部定义和调用函数](#8.3 在函数内部定义和调用函数)
[9. 总结](#9. 总结)
1. 什么是函数?
函数是一段可重复使用的代码块,用于完成特定任务。它接受输入(参数),执行一系列操作,然后返回输出(返回值)。可以把函数理解为一个"代码机器":你把原料(参数)放进去,它经过内部处理,产出结果(返回值)。
Python中函数的定义使用 def 关键字,基本语法如下:
python
def 函数名(参数列表):
"""文档字符串(可选)"""
函数体
return 返回值 # 可选
一个简单的例子:
python
def greet(name):
"""向指定的人打招呼"""
return f"你好,{name}!"
print(greet("小明")) # 输出:你好,小明!
2. 为什么需要函数?
在编程中使用函数有以下几个重要原因:
- 代码复用:一次定义,多次调用,避免重复编写相同逻辑。比如计算圆面积的代码,写成函数后每次只需一行调用即可。
- 模块化:将复杂问题拆解成若干小函数,每个函数只负责一件事,使程序结构清晰、易于理解。
- 可维护性:当需要修改某段逻辑时,只需修改对应的函数,所有调用处自动生效,降低维护成本。
- 可测试性:独立的小函数便于编写单元测试,快速定位问题。
- 可读性:良好的函数命名能直观表达代码意图,让代码像自然语言一样易读。
举个例子,没有函数时重复代码会很多:
python
# 没有函数,代码重复
radius1 = 5
area1 = 3.14 * radius1 * radius1
print(f"圆面积:{area1}")
radius2 = 10
area2 = 3.14 * radius2 * radius2
print(f"圆面积:{area2}")
使用函数后简洁且优雅:
python
def circle_area(radius):
return 3.14 * radius * radius
print(f"圆面积:{circle_area(5)}")
print(f"圆面积:{circle_area(10)}")
3. 函数的分类
Python中的函数可以大致分为三类:内置函数 、模块函数 和自定义函数。
3.1 内置函数
内置函数是Python解释器自带的、无需导入即可直接使用的函数。它们是Python核心的一部分,涵盖了常见的基础操作。常见的内置函数包括:
print():输出内容到控制台len():返回对象的长度type():返回对象的类型int()、float()、str():类型转换函数range():生成整数序列input():接收用户输入max()、min()、sum():数学统计函数sorted()、reversed():序列操作函数enumerate()、zip():迭代辅助函数map()、filter():高阶函数
python
# 内置函数使用示例
numbers = [3, 1, 4, 1, 5, 9]
print(len(numbers)) # 6
print(max(numbers)) # 9
print(sorted(numbers)) # [1, 1, 3, 4, 5, 9]
3.2 模块函数
模块函数是存放在Python标准库或第三方库中的函数,需要通过 import 导入后才能使用。Python拥有丰富的标准库,覆盖了数学计算、文件操作、网络通信、数据处理等各个领域。
python
import math
import random
from datetime import datetime
print(math.sqrt(16)) # 4.0
print(random.randint(1, 100)) # 随机整数
print(datetime.now()) # 当前日期时间
常见的模块及其函数:
math模块:sqrt()、sin()、cos()、log()、pi等random模块:randint()、choice()、shuffle()、random()等os模块:getcwd()、listdir()、path.join()等json模块:dumps()、loads()、dump()、load()等
3.3 自定义函数
自定义函数是开发者根据实际需求自己编写的函数,使用 def 关键字定义。这是日常开发中最频繁使用的函数类型,能够将业务逻辑封装为可复用的单元。
python
def calculate_bmi(weight, height):
"""计算BMI指数"""
return weight / (height ** 2)
def is_prime(n):
"""判断是否为质数"""
if n < 2:
return False
for i in range(2, int(n ** 0.5) + 1):
if n % i == 0:
return False
return True
4. 函数参数详解
4.1 形参与实参
在函数定义和调用中,参数有两个重要概念:
- 形参(形式参数) :函数定义时括号中的变量名,它是占位符,表示函数期望接收的数据。例如
def add(a, b)中的a和b就是形参。 - 实参(实际参数) :函数调用时实际传入的具体值。例如
add(3, 5)中的3和5就是实参。
python
def multiply(x, y): # x 和 y 是形参
return x * y
result = multiply(4, 7) # 4 和 7 是实参
print(result) # 28
4.2 位置传参
位置传参是最基本的传参方式,实参按照位置顺序依次传递给对应的形参。第1个实参给第1个形参,第2个实参给第2个形参,以此类推。
python
def describe_person(name, age, city):
print(f"{name}今年{age}岁,来自{city}")
位置传参:按顺序匹配
describe_person("小明", 18, "北京")
输出:小明今年18岁,来自北京
顺序错误会导致结果异常
describe_person("北京", 18, "小明")
输出:北京今年18岁,来自小明
4.3 关键字传参
关键字传参通过"形参名=值"的方式传递实参,不依赖位置顺序,代码更具可读性。
python
def describe_person(name, age, city):
print(f"{name}今年{age}岁,来自{city}")
关键字传参:顺序无关
describe_person(age=18, city="上海", name="小红")
输出:小红今年18岁,来自上海
关键字传参的优势在于:参数多时不易混淆顺序,代码自解释性强,还能和位置传参混合使用(位置参数必须放在关键字参数之前)。
python
# 混合使用:位置参数在前,关键字参数在后
describe_person("小明", city="广州", age=20)
# 输出:小明今年20岁,来自广州
4.4 限制传参方式
Python 3.8+ 提供了两种特殊语法来限制参数的传递方式:
- 仅限位置参数 :在
/之前的参数只能通过位置方式传递,不能使用关键字。 - 仅限关键字参数 :在
*之后的参数只能通过关键字方式传递,不能使用位置。
python
def func(a, b, /, c, d, *, e, f):
"""
a, b:仅限位置传参(在 / 前面)
c, d:位置或关键字均可
e, f:仅限关键字传参(在 * 后面)
"""
print(f"a={a}, b={b}, c={c}, d={d}, e={e}, f={f}")
正确调用
func(1, 2, 3, 4, e=5, f=6)
func(1, 2, c=3, d=4, e=5, f=6)
错误调用示例:
func(a=1, b=2, 3, 4, e=5, f=6) # a和b不能使用关键字
func(1, 2, 3, 4, 5, 6) # e和f必须使用关键字
这种限制在实际开发中非常有用。比如 open() 函数的文件路径就是仅限位置参数,而 sorted() 的 key 和 reverse 参数则设计为仅限关键字参数,避免调用歧义。
4.5 默认参数
默认参数是在函数定义时给形参指定一个默认值。调用时如果未传递该参数,则使用默认值;如果传递了,则覆盖默认值。默认参数让函数调用更灵活。
python
def greet(name, greeting="你好"):
return f"{greeting},{name}!"
print(greet("小明")) # 你好,小明!
print(greet("Tom", greeting="Hello")) # Hello,Tom!
重要注意事项:默认参数的值在函数定义时只计算一次。如果默认值是可变对象(如列表、字典),可能会引发意想不到的问题:
python
# 错误示范:默认参数使用可变对象
def add_item(item, items=[]):
items.append(item)
return items
print(add_item("a")) # ['a']
print(add_item("b")) # ['a', 'b'] ------ 不是预期的 ['b']!
正确做法:使用None作为默认值
def add_item(item, items=None):
if items is None:
items = []
items.append(item)
return items
print(add_item("a")) # ['a']
print(add_item("b")) # ['b']
另外,默认参数必须放在非默认参数的后面,否则会报语法错误。
python
# 正确:默认参数在后面
def func(a, b=10):
pass
错误:默认参数在前面
def func(a=10, b):
pass # SyntaxError
4.6 不定长参数(*args 和 **kwargs)
当不确定函数会接收多少个参数时,可以使用不定长参数:
*args:接收任意数量的位置参数 ,将它们打包成一个元组。**kwargs:接收任意数量的关键字参数 ,将它们打包成一个字典。
这里的名称 args 和 kwargs 只是约定俗成的命名,真正起作用的是 * 和 ** 符号,你可以使用其他名称,但建议遵循惯例。
python
# *args:接收多个位置参数
def sum_all(*args):
"""计算任意个数字的和"""
total = 0
for num in args:
total += num
return total
print(sum_all(1, 2, 3)) # 6
print(sum_all(1, 2, 3, 4, 5)) # 15
**kwargs:接收多个关键字参数
def print_info(**kwargs):
"""打印任意关键字参数"""
for key, value in kwargs.items():
print(f"{key}: {value}")
print_info(name="小明", age=18, city="北京")
输出:
name: 小明
age: 18
city: 北京
它们也可以组合使用,此时参数顺序有严格要求:普通参数 → *args → 默认参数 → **kwargs。
python
def full_func(a, b, *args, c=10, **kwargs):
print(f"a={a}, b={b}")
print(f"args={args}")
print(f"c={c}")
print(f"kwargs={kwargs}")
full_func(1, 2, 3, 4, 5, c=20, x=100, y=200)
输出:
a=1, b=2
args=(3, 4, 5)
c=20
kwargs={'x': 100, 'y': 200}
5. 参数的打包与解包
参数的打包与解包是Python中非常灵活的特性,使用 * 和 ** 操作符可以实现:
- 打包 :函数定义时,将多个实参收集到一个变量中(即上面讲的
*args和**kwargs)。 - 解包:函数调用时,将序列或字典中的元素拆开,作为独立的实参传递。
5.1 序列解包(* 用于列表/元组)
python
def multiply(a, b, c):
return a * b * c
有一个列表,想把元素分别传给三个参数
numbers = [2, 3, 4]
result = multiply(*numbers) # 等价于 multiply(2, 3, 4)
print(result) # 24
元组解包同理
values = (5, 6, 7)
print(multiply(*values)) # 210
5.2 字典解包(** 用于字典)
python
def greet(name, age, city):
print(f"{name},{age}岁,来自{city}")
字典的键必须和形参名一致
person = {"name": "小明", "age": 18, "city": "深圳"}
greet(**person) # 等价于 greet(name="小明", age=18, city="深圳")
输出:小明,18岁,来自深圳
5.3 打包与解包结合使用
python
def calculate(op, *numbers):
"""对任意个数字执行指定操作"""
if op == "sum":
return sum(numbers)
elif op == "product":
result = 1
for n in numbers:
result *= n
return result
列表解包后传入,函数内部再次打包接收
data = [1, 2, 3, 4, 5]
print(calculate("sum", *data)) # 15
print(calculate("product", *data)) # 120
解包操作在日常开发中非常实用,比如合并列表、合并字典、灵活地传递参数等:
python
# 合并列表
list1 = [1, 2, 3]
list2 = [4, 5, 6]
combined = [*list1, *list2]
print(combined) # [1, 2, 3, 4, 5, 6]
合并字典(Python 3.5+)
dict1 = {"a": 1, "b": 2}
dict2 = {"c": 3, "d": 4}
merged = {**dict1, **dict2}
print(merged) # {'a': 1, 'b': 2, 'c': 3, 'd': 4}
6. 函数的返回值
函数通过 return 语句将计算结果返回给调用者。关于返回值需要掌握以下要点:
6.1 基本用法
python
def add(a, b):
return a + b
result = add(3, 5)
print(result) # 8
print(result * 2) # 16,返回值可以参与后续运算
6.2 返回多个值
Python函数可以返回多个值,本质上是返回一个元组,接收时可以直接用多个变量解包:
python
def get_user_info():
name = "小明"
age = 18
city = "杭州"
return name, age, city # 实际返回的是一个元组
多变量解包接收
n, a, c = get_user_info()
print(n, a, c) # 小明 18 杭州
也可以用一个变量接收整个元组
info = get_user_info()
print(info) # ('小明', 18, '杭州')
print(type(info)) # <class 'tuple'>
6.3 没有return语句的情况
如果函数没有 return 语句,或者 return 后面没有跟值,函数默认返回 None。
python
def say_hello(name):
print(f"你好,{name}!")
# 没有return,默认返回None
result = say_hello("小明")
print(result) # None
print(type(result)) # <class 'NoneType'>
6.4 return终止函数执行
return 语句执行后,函数立即结束,后面的代码不会被执行:
python
def check_age(age):
if age < 0:
return "年龄不能为负数" # 函数在此结束
if age < 18:
return "未成年"
return "已成年"
print(check_age(-5)) # 年龄不能为负数
print(check_age(15)) # 未成年
print(check_age(25)) # 已成年
7. 局部变量与全局变量
7.1 局部变量
在函数内部定义的变量称为局部变量,其作用域仅限于该函数内部。函数执行完毕后,局部变量会被销毁,外部无法访问。
python
def my_func():
x = 100 # 局部变量,只在函数内部有效
print(f"函数内部:x = {x}")
my_func() # 函数内部:x = 100
print(x) # NameError: name 'x' is not defined
不同函数中的局部变量相互独立,互不影响:
python
def func_a():
x = 10
print(f"func_a中:x = {x}")
def func_b():
x = 20
print(f"func_b中:x = {x}")
func_a() # func_a中:x = 10
func_b() # func_b中:x = 20
7.2 全局变量
在函数外部、模块层级定义的变量称为全局变量,可以在整个程序的任何位置访问。
python
total = 0 # 全局变量
def add_to_total(value):
print(total) # 可以读取全局变量
add_to_total(10) # 0
但是,如果要在函数内部修改 全局变量,需要使用 global 关键字声明:
python
total = 0
def add_to_total(value):
global total # 声明要修改全局变量
total += value
add_to_total(10)
print(total) # 10
add_to_total(5)
print(total) # 15
如果没有 global 声明就进行赋值操作,Python会将其视为创建了一个新的局部变量,而不是修改全局变量:
python
total = 0
def add_to_total(value):
# 没有global声明
total = value # 这里创建的是局部变量!
print(f"函数内部total:{total}")
add_to_total(10) # 函数内部total:10
print(f"全局total:{total}") # 全局total:0 ------ 全局变量没有被修改
7.3 嵌套函数中的变量:nonlocal
在嵌套函数中,内层函数想要修改外层函数(但不是全局)的变量时,需要使用 nonlocal 关键字:
python
def outer():
count = 0 # 外层函数的局部变量
def inner():
nonlocal count # 声明要修改外层函数的变量
count += 1
return count
return inner
counter = outer()
print(counter()) # 1
print(counter()) # 2
print(counter()) # 3
这段代码展示了闭包(Closure)的基本用法,inner 函数记住了外层 count 变量的状态。
8. 函数中调用函数
函数可以在内部调用其他函数,这体现了代码复用的思想。主要包括两种形式:普通嵌套调用 和递归调用。
8.1 普通嵌套调用
一个函数在其内部调用另一个函数来完成部分任务,这是日常开发中最常见的模式:
python
def is_even(n):
"""判断是否为偶数"""
return n % 2 == 0
def is_positive(n):
"""判断是否为正数"""
return n > 0
def classify_number(n):
"""综合判断一个数"""
if not is_positive(n):
return "负数或零"
if is_even(n):
return "正偶数"
else:
return "正奇数"
print(classify_number(10)) # 正偶数
print(classify_number(7)) # 正奇数
print(classify_number(-3)) # 负数或零
8.2 递归调用
递归是指一个函数在其内部调用自身 。递归是解决具有自相似结构问题的强大工具,但必须正确设置终止条件,否则会导致无限递归。
经典的递归例子------计算阶乘:
python
def factorial(n):
"""递归计算阶乘"""
if n == 0 or n == 1: # 终止条件
return 1
return n * factorial(n - 1) # 递归调用
print(factorial(5)) # 120 (5 × 4 × 3 × 2 × 1)
斐波那契数列也是递归的经典应用:
python
def fibonacci(n):
"""返回第n个斐波那契数"""
if n <= 1:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
for i in range(10):
print(fibonacci(i), end=" ")
输出:0 1 1 2 3 5 8 13 21 34
需要注意的是,递归虽然代码简洁,但可能有性能问题(重复计算)和栈溢出风险。实际问题中常用记忆化递归 或直接改用迭代来优化:
python
# 迭代版阶乘,性能更好
def factorial_iterative(n):
result = 1
for i in range(2, n + 1):
result *= i
return result
print(factorial_iterative(5)) # 120
8.3 在函数内部定义和调用函数
Python支持在函数内部定义另一个函数(嵌套函数),这在创建闭包、装饰器等场景中非常有用:
python
def make_multiplier(factor):
"""创建一个乘法器"""
def multiplier(x):
# 内层函数可以访问外层函数的factor参数
return x * factor
return multiplier
double = make_multiplier(2)
triple = make_multiplier(3)
print(double(10)) # 20
print(triple(10)) # 30
9. 总结
函数是Python编程中最重要的概念之一,本文从基础到进阶系统梳理了以下内容:
- 函数的定义 :使用
def关键字,由函数名、参数列表和函数体组成。 - 函数的分类:内置函数(开箱即用)、模块函数(需要导入)、自定义函数(按需编写)。
- 参数体系:形参与实参的区别、位置传参与关键字传参、仅限位置/仅限关键字参数的限制、默认参数的灵活性、*args和**kwargs不定长参数。
- 打包与解包 :
*和**在定义时打包、在调用时解包,让参数传递极其灵活。 - 返回值 :
return语句返回结果,可返回多个值(元组),没有return时默认返回None。 - 变量作用域 :局部变量只在函数内部可见,全局变量通过
global声明才能在函数内修改,嵌套函数用nonlocal修改外层变量。 - 函数的互相调用:嵌套调用体现代码复用,递归调用解决自相似问题,嵌套函数定义用于闭包等高级场景。
掌握好函数的使用,是写出高质量Python代码的关键一步。建议读者结合本文中的代码示例进行动手实践,逐步加深理解。
两个易错题:
python
# 函数的默认参数,只会在定义函数的瞬间,创建一次。后面默认调用,一直复用一个列表
def test(a, lst1=[1, 2]): # 定义一个函数
# 把a添加到列表中
if a not in lst1:
lst1.append(a)
return lst1
print(f'第一次调用test函数的结果是:{test(10)}') # [1, 2, 10]
print(f'第二次调用test函数的结果是:{test(20)}') # [1, 2, 10, 20]
print(f'第三次调用test函数的结果是:{test(30, lst1=[60, 70])}') # [60, 70, 30]
print(f'第四次调用test函数的结果是:{test(40)}') #[1, 2, 10, 20, 40]
函数的默认参数,只会在定义函数的瞬间,创建一次。后面调用,一直复用一个列表。所以之前的函数调用,对默认list的改变一直有存储。
python
# x的指向一直是10,10是不可变的。
def test(x=10):
x += 1
print(x)
test() # 11
test() # 11
test() # 11
运行三次,值不变,跟上一题的规则并不冲突。x变量也是创建一次,但是10是个字面常量,是不可变的。第一题,lst1也是创建了一次,指向的地址也是不会再变的,但是地址所代表的列表list是可变的。所以此调用函数,变量x都是10。