在编写程序时,经常会遇到一些需要反复执行的代码块。如果每次都重写一遍,不仅效率低,而且代码冗余、难以维护。
函数正是为了解决这个问题而诞生的——它将一段具有独立功能的代码封装起来,并赋予一个名字,需要时直接调用即可。
Python中的函数非常灵活,可以在模块中定义、在函数中定义(嵌套函数),甚至在类中定义(方法)。
本章将从零开始,带你掌握函数的定义、调用、参数、作用域以及高阶函数等核心知识。
一、定义与调用函数
# 1.1 基本定义
使用def 关键字定义函数,语法格式如下:
def 函数名(参数列表):
函数体
return 返回值
- 函数名:遵循标识符规则,见名知意。
- 参数列表:可有0个或多个形式参数(简称形参)。
- 函数体:缩进的代码块,实现具体功能。
- return:返回结果,如果没有 return 则返回 None。
案例:ch8_1.py 中的两个函数
python
# 定义计算面积的函数,返回面积值
def rect_area(width, height):
area = width * height
return area
# 定义打印面积的函数,不返回值(只打印)
def print_area(width, height):
area = width * height
print(f'{width} * {height} 长方形的面积:{area:.2f}')
- rect_area:有参数、有返回值,调用后可以得到计算结果。
- print_area:有参数、无返回值,直接打印结果。
# 1.2 调用函数
调用函数时传递给函数的具体数据称为实际参数(实参)。实参与形参在数量、类型上应匹配。
位置参数调用:实参与形参的顺序一致。
python
r_area = rect_area(320, 480) # 位置参数:320→width, 480→height
print(f'{320} * {480} 长方形的面积:{r_area:.2f}')
关键字参数调用:以形参名=值 的形式传递,可以不按顺序。
python
r_area = rect_area(width=320, height=480) # 关键字,顺序无所谓
r_area = rect_area(height=480, width=320) # 交换顺序也可以
print(f'{320} * {480} 长方形的面积:{r_area:.2f}')
关键字参数让代码更易读,尤其在参数较多时非常实用。
二、参数的默认值
Python 没有 C++ 中的函数重载(同名不同参的函数),但可以通过参数的默认值实现类似的效果。在定义函数时,可以为形参指定默认值,这样调用时如果不传该参数,就使用默认值。
案例:ch8_3.py 制作咖啡
python
def make_coffee(name='卡布奇诺'):
return f'制作一杯{name}咖啡'
coffee1 = make_coffee('拿铁') # 传参,使用'拿铁'
coffee2 = make_coffee() # 不传参,使用默认值'卡布奇诺'
print(coffee1) # 制作一杯拿铁咖啡
print(coffee2) # 制作一杯卡布奇诺咖啡
注意:有默认值的参数必须放在参数列表的末尾(即无默认值的参数在前,有默认值的在后)。
三、可变参数
有时我们不确定调用者会传入多少个参数,这时可以使用可变参数。Python 支持两种可变参数:基于元组的 *args 和 基于字典的 kwargs。
# 3.1 基于元组的可变参数(*)
在参数名前加一个*,该参数会将所有传入的位置参数打包成一个元组。
案例:ch8_4_1.py 计算任意多个数的和
python
def sum(*numbers):
total = 0.0
for number in numbers:
total += number
return total
print(sum(100.0, 20.0, 30.0)) # 输出 150.0
print(sum(30.0, 80.0)) # 输出 110.0
- 第一次调用传入三个数,numbers 元组为 (100.0, 20.0, 30.0)。
- 第二次传入两个数,numbers 元组为 (30.0, 80.0)。
- 函数内部遍历元组,累加求和。
# 3.2 基于字典的可变参数()
在参数名前加两个*,该参数会将所有传入的关键字参数打包成一个字典。
案例:ch8_4_2.py 显示任意多个个人信息
python
def show_info(info):
print('--------show_info------------')
for key, value in info.items():
print(f'{key} - {value}')
show_info(name='Tony', age=18, sex=True)
show_info(student_name='Tony', student_no='1000')
- 第一次调用传入三个关键字参数,info 字典为 {'name':'Tony', 'age':18, 'sex':True}。
- 第二次调用传入两个,字典为 {'student_name':'Tony', 'student_no':'1000'}。
- 函数遍历字典并打印键值对。
四、变量的作用域
变量根据定义位置分为全局变量和局部变量。
- 全局变量:在函数外部(模块顶层)定义,整个文件都可以访问。
- 局部变量:在函数内部定义,只在函数内有效。函数内的同名变量会覆盖全局变量。
案例:ch8_5.py 局部变量不影响全局
python
x = 20 # 全局变量
def print_value():
x = 10 # 局部变量,与全局的 x 不同
print(f"函数中x = {x}")
print_value() # 函数中x = 10
print(f"全局变量x = {x}") # 全局变量x = 20
# 4.1 使用 global 修改全局变量
如果需要在函数内部修改全局变量,必须先用global 声明。
案例:ch8_5_2.py
python
x = 20
def print_value():
global x # 声明使用全局变量 x
x = 10 # 此时修改的是全局变量
print('函数中x ={0}'.format(x))
print_value() # 函数中x = 10
print('全局变量中x = {}'.format(x)) # 全局变量中x = 10
尽量少用 global,因为它会使程序逻辑复杂,难以调试。推荐将需要修改的值通过参数传递和返回值的方式处理。
五、函数类型与高阶函数
在Python 中,函数也是一种数据类型(function 类型)。这意味着:
- 函数可以作为返回值返回。
- 函数可以作为参数传递给另一个函数。
# 5.1 函数作为返回值
根据不同的条件,动态返回不同的函数。
案例:ch8_6_1.py 计算器
python
def add(a, b):
return a + b
def sub(a, b):
return a - b
def calc(opr):
if opr == '+':
return add # 返回函数对象,不加括号
else:
return sub
f1 = calc('+') # f1 现在等于 add 函数
f2 = calc('-') # f2 等于 sub 函数
print("10 + 5 =", f1(10, 5)) # 调用 f1,即调用 add
print("10 - 5 =", f2(10, 5)) # 调用 f2
# 5.2 函数作为参数:filter() 过滤函数
filter(function, iterable) 将 function 依次作用于 iterable 的每个元素,保留那些使 function 返回 True 的元素。
案例:ch8_6_2.py 过滤大于50的数
python
def f1(x):
return x > 50 # 条件:大于50
data1 = [66, 15, 91, 28, 98, 50, 7, 80, 99]
filtered = filter(f1, data1)
data2 = list(filtered)
print(data2) # [66, 91, 98, 80, 99]
- filter() 返回一个迭代器,用 list() 转为列表。
- 这个模式在数据清洗中非常常见,例如 ETF 量化投资中过滤掉成交量过低或价格异常的数据。
# 5.3 函数作为参数:map() 映射函数
map(function, iterable) 将 function 依次作用于 iterable 的每个元素,并返回一个包含所有结果的新迭代器。
案例:ch8_6_3.py 将列表每个元素乘以2
python
def f1(x):
return x * 2
data1 = [66, 15, 91, 28, 98, 50, 7, 80, 99]
mapped = map(f1, data1)
data2 = list(mapped)
print(data2) # [132, 30, 182, 56, 196, 100, 14, 160, 198]
在量化投资模型中,map 常用于批量计算指标(如将收益率转换为复权价格)。
六、lambda 匿名函数
当函数逻辑非常简单,且只在一个地方使用时,可以用lambda 关键字定义匿名函数。语法:
lambda 参数列表: 表达式
- 不需要 def 和函数名。
- 表达式的结果就是返回值。
- 通常作为高阶函数(如 filter、map)的参数。
案例:ch8_8.py 用 lambda 重写过滤和映射
python
data1 = [66, 15, 91, 28, 98, 50, 7, 80, 99]
# 过滤:lambda x: x > 50 等价于之前的 f1
filtered = filter(lambda x: (x > 50), data1)
data2 = list(filtered)
print(data2) # [66, 91, 98, 80, 99]
# 映射:lambda x: x * 2
mapped = map(lambda x: (x * 2), data1)
data3 = list(mapped)
print(data3) # [132, 30, 182, 56, 196, 100, 14, 160, 198]
案例:ch8_8_7.py lambda 作为返回值
python
def calc(opr):
if opr == "+":
return lambda a, b: (a + b) # 返回一个加法 lambda
else:
return lambda a, b: (a - b) # 返回减法 lambda
f1 = calc('+')
f2 = calc('-')
print("10 + 5 =", f1(10, 5)) # 10 + 5 = 15
print("10 - 5 =", f2(10, 5)) # 10 - 5 = 5
lambda 让代码更简洁,但不能包含复杂的逻辑(如循环、多行语句)。如果逻辑稍复杂,还是应该用普通函数。
七、总结
- 定义函数:def + 函数名 + 参数列表 + 函数体 + return。
- 调用方式:位置参数(按顺序)、关键字参数(指定形参名)。
- 默认参数:为形参提供默认值,实现类似重载的效果。
- 可变参数:
- *args:接收任意多个位置参数,打包成元组。
- kwargs:接收任意多个关键字参数,打包成字典。
- 作用域:局部变量在函数内有效,全局变量在模块内有效。global 可在函数内修改全局变量(谨慎使用)。
- 函数类型:函数是第一类对象,可以赋值、作为参数、作为返回值。
- 高阶函数:filter() 过滤、map() 映射。
- lambda 函数:单行匿名函数,常用于高阶函数的参数。
下一章我们将学习面向对象编程——类与对象。