Python系列(6)- Python 函数、Python 装饰器
函数在数学上的定义:给定一个非空的数集 A,对 A 施加对应法则 f,记作 f(A),得到另一数集 B,也就是 B = f(A),那么这个关系式就叫函数关系式,简称函数。
简而言之,两个变量 x 和 y,如果每给定 x 的一个值,y 都有一个确定的值与其对应,那么我们就说 y 是 x 的函数。其中,x 叫做自变量,y 叫做因变量。
函数的抽象定义:一个输入产生一个输出,那么这个输出就是输入的函数,输入和输出之间的这种关系叫做函数映射。
计算机函数(或称为程序函数、子程序等)是编程中的一个重要概念,它封装了一段代码,用于完成特定的任务或计算。计算机函数可以接受输入(参数),执行一系列操作,并返回结果(如果有的话)。计算机函数的设计旨在提高代码的重用性、可读性和可维护性。
计算机函数和数学函数都描述了一种 “输入-> 输出” 的对应关系。在数学中,这种对应关系是抽象的、理论的;在计算机中,这种对应关系是通过具体的代码实现的。
数学函数通常遵循严格的数学规则和逻辑,其定义和性质是独立于具体实现方式的。计算机函数则依赖于具体的编程语言、算法和数据结构来实现。计算机函数可能需要对数学函数进行近似、离散化或优化,以适应计算机的计算能力和存储限制。
1. Python 函数
Python 函数是计算机函数的一种具体实现,它们在概念和功能上与计算机函数相似,但 Python 提供了更多的灵活性和高级功能,使得程序员能够以更加模块化和可重用的方式组织代码。
在 Python 语言中,函数是组织和重用代码的基本方式之一。
Python 函数具有以下特性:
(1) 函数是组织代码的方式,使得代码更加模块化和可重用。
(2) 函数可以接受输入参数,这些参数可以是必需的、默认的或可变数量的。
(3) 函数可以有返回值,函数执行完毕后可以返回结果。
(4) 函数可以有文档字符串,用于描述函数的功能和使用方法。
(5) 函数是 Python 面向对象编程中方法的基本单元。
Python 函数的主要作用:
(1) 代码重用:函数可以将一段代码封装起来,以便在多个地方重复使用。这样可以提高代码的复用性,减少代码的冗余。
(2) 模块化编程:函数可以将复杂的程序分解成多个小的模块,每个模块负责完成一个特定的功能。这样可以使程序结构更加清晰,易于维护和扩展。
(3) 提高代码可读性:函数可以将一段代码命名为一个有意义的名称,使代码更易于理解和阅读。函数还可以添加注释,进一步解释代码的作用和实现方式。
(4) 提高代码的可维护性:函数可以将一段代码封装起来,使其成为一个独立的单元。这样在修改和调试代码时,只需要关注函数的实现细节,而不需要关心其他部分的代码。
(5) 提高代码的可测试性:函数可以独立于其他代码进行测试,这样可以更方便地验证函数的正确性。通过编写测试用例,可以快速发现和修复函数中的错误。
2. 函数的定义和调用
Python 定义函数使用 def 关键字,语法格式如下:
def 函数名([参数列表]):
功能代码
[return [返回值]]
名词解释:
(1) 函数名:一个符合 Python 语法的标识符,函数名要能够体现出该函数的功能;
(2) [参数列表]:可选项,多个参数之间用逗号分隔,这里的参数就是形参;
(3) [return [返回值] ]:可选项,返回值可以是各种数据类型,可以返回多个值,各值用逗号分隔。
注:[] 括表示非必选项。
调用函数(即执行函数),语法格式如下:
[变量] = 函数名([参数列表])
名词解释:
(1) 函数名: 就是定义函数时的函数名;
(2) [参数列表]: 可选项,多个参数之间用逗号分隔,这里的参数就是实参;
(3) [变量]: 可选项,如果函数没有返回值,可以不定义变量接收返回值。
示例:
#!/usr/bin/python3 def func(a, b): return a+b,a-b if __name__ == "__main__": c, d = func(1, 2) print(f'a + b = {c}') print(f'a - b = {d}')
输出结果如下:
a + b = 3
a - b = -1
3. 函数参数
1) 位置参数
位置参数 (或称必选参数),是指必须按照正确的顺序将实际参数传到函数中,即调用函数时传入实际参数的数量和位置都必须和定义函数时保持一致。
示例:
#!/usr/bin/python3 def func(a, b): return a+b if __name__ == "__main__": c = func(1, 2, 3) print(f'a + b = {c}')
输出结果如下:
Traceback (most recent call last): File "d:/pythonDemo/func.py", line 8, in <module> c = func(1, 2, 3) TypeError: func() takes 2 positional arguments but 3 were given
注:func 需要 2 个参数,调用 func 时传了 3 个参数。
2) 默认参数
Python 函数可以为参数设置默认值,即在定义函数时,直接给形参设置一个默认值。调用函数时没给设置了默认值的形参传值,该参数自动使用设置的默认值。
示例:
#!/usr/bin/python3 def func(a, b=3): return a+b if __name__ == "__main__": c = func(1, 2) print(f'a + b = {c}') d = func(1) print(f'a + b = {d}')
输出结果如下:
a + b = 3
a + b = 4
注:带默认值的参数必须处于不带默认值的参数的后面(从左到右排序)
可以使用 “函数名.__defaults__” 查看函数参数的默认值,其返回值是一个元组。执行如下代码:
print(func.__defaults__)
输出结果如下:
(3,)
3) 可变参数
可变参数 (或称不定长参数),即参数的数量是弹性的,不必在声明函数时对所有参数进行定义,用循环实现,在形参前加一个星号 *,则可以表示该参数是一个不定长参数。
示例:
#!/usr/bin/python3 def func(*params): print(params) if __name__ == "__main__": func(1) func(1, 2, 3) func(*[4,5,6])
输出结果如下:
(1,)
(1, 2, 3)
(4, 5, 6)
注:形参前面带 *,表示可变参数;实参前面带 *,表示 * 后面必须是一个 list 或 touple,比如:func(*[4,5,6]) 等效于 func(4,5,6) 。
4) 关键字参数
关键字参数是根据形参的名字来确定输入的参数值。函数调用传递实参时,不需要与形参的位置完全一致,只要将形参的名称写正确即可。
示例:
#!/usr/bin/python3 def func(name, age, **description): print('name: ', name, ' age: ', age, ' description: ', description) if __name__ == "__main__": func(age=18, name='Python', city='Shanghai', job='Engineer') func(age=20, name='Java', **{'city': 'Beijing', 'job': 'Designer'})
输出结果如下:
name: Python age: 18 description: {'city': 'Shanghai', 'job': 'Engineer'}
name: Java age: 20 description: {'city': 'Beijing', 'job': 'Designer'}
注:形参前面带 **,表示关键字参数, 以 key=value 的格式输入;实参前面带 **, 表示 ** 后面必须是一个 dict 。
5) 命名关键字参数
一个特殊分隔符 * 参数,该参数后面的参数被视为命名关键字参数。
示例:
#!/usr/bin/python3 def func(name, age, *,city, job): print('name: ', name, ' age: ', age, ' city: ', city, ' job:', job) if __name__ == "__main__": func('Python', 18, city='Shanghai', job='Engineer')
输出结果如下:
name: Python age: 18 city: Shanghai job: Engineer
注:如果第3个 * 参数本身就是一个可变参数 (带 *),该可变参数后面的参数就是命名关键字参数,无需再添加单独的 * 参数。
6) 参数组合使用
在 Python 中定义函数,可以用位置参数、默认参数、可变参数、关键字参数和命名关键字参数,这 5 种参数都可以组合使用。
参数定义的顺序是(从左到右排序):位置参数、默认参数、可变参数、命名关键字参数和关键字参数。
示例:
#!/usr/bin/python3 def func(a, b=3, *params, name, age): print(params) print('name: ', name, ' age: ', age) return a+b if __name__ == "__main__": c = func(1, 2, *[4, 5, 6], name='Python', age=9) print(f'a + b = {c}')
输出结果如下:
(4, 5, 6)
name: Python age: 9
a + b = 3
7) 值传递和引用传递
在 Python 中,根据实际参数的类型不同,函数参数的传递方式可分为 2 种,分别为值传递和引用(地址)传递:
(1) 值传递:适用于实参类型为不可变类型(字符串、数字、元组);
(2) 引用(地址)传递:适用于实参类型为可变类型(列表,字典);
值传递和引用传递的区别是,函数参数进行值传递后,若形参的值发生改变,不会影响实参的值;而函数参数继续引用传递后,改变形参的值,实参的值也会一同改变。
示例:
#!/usr/bin/python3 # -*- coding: UTF-8 -*- def func(p1, p2, p3, p4, p5): p1 = 'Hello Python2' p2 = 9 p3 = (7,8) p4[1] = 'age2' p5['city'] = 'Beijing' if __name__ == "__main__": v1 = 'Hello Python' v2 = 1 v3 = (2,3) v4 = ['name1', 'name2'] v5 = { 'city': 'Shanghai'} print(v1) print(v2) print(v3) print(v4) print(v5) func(v1, v2, v3, v4, v5) print(v1) print(v2) print(v3) print(v4) print(v5)
输出结果如下:
Hello Python 1 (2, 3) ['name1', 'name2'] {'city': 'Shanghai'} Hello Python 1 (2, 3) ['name1', 'age2'] {'city': 'Beijing'}
4. 特殊函数
1) lambda 表达式(匿名函数)
lambda 表达式,又称匿名函数,常用来表示内部仅包含 1 行表达式的函数。如果一个函数的函数体仅有 1 行表达式,则该函数就可以用 lambda 表达式来代替。
lambda 表达式的格式如下:
name = lambda [list] : 表达式
定义 lambda 表达式必须使用 lambda 关键字,[list] 作为可选参数(等同于函数的参数列表),name 为该表达式的名称。
示例:
#!/usr/bin/python3 if __name__ == "__main__": addsub = lambda a,b:(a+b,a-b) c,d = addsub(1, 2) print(f'a + b = {c}') print(f'a - b = {d}')
输出结果如下:
a + b = 3
a - b = -1
lambda 表达式,其就是简单函数(函数体为单行的表达式)的简写版本。对于单行函数,使用 lambda 表达式可以省去定义函数的过程,让代码更加简洁。对于不需要多次复用的函数,使用 lambda 表达式可以在用完之后立即释放,提高程序执行的性能。
2) 局部函数
Python 支持在函数内部定义函数,此类函数称为局部函数,或称为嵌套函数。
示例:
#!/usr/bin/python3 # -*- coding: UTF-8 -*- def func(n): # 局部函数定义 def inner_func(): return f'Local function inner_func({n})' # 局部函数调用 return inner_func() if __name__ == "__main__": f1 = func(1) f2 = func(2) print(f1) print(f2)
输出结果如下:
Local function inner_func(1)
Local function inner_func(2)
注:func(n) 函数被调用时,func(n) 调用了其内部的局部函数 inner_func(),inner_func() 读取和使用了 n 的实时值,生成字符串返回值。
3) 闭包函数
闭包函数(或称闭合函数,又称闭包),闭包函数是一个局部函数,它的不同之处在于,闭包返回的不是值,而是一个函数引用(或指针)。
示例:
#!/usr/bin/python3 # -*- coding: UTF-8 -*- def func(n): # 闭包函数定义 def inner_func(): return f'Local function inner_func({n})' return inner_func if __name__ == "__main__": f1 = func(1) f2 = func(2) print(f1()) print(f2()) print(f1.__closure__) print(f2.__closure__)
输出结果如下:
Local function inner_func(1)
Local function inner_func(2)
(<cell at 0x0000025A01282730: int object at 0x00007FFA011D16A0>,)
(<cell at 0x0000025A012826D0: int object at 0x00007FFA011D16C0>,)
注:func(n) 函数被调用时,func(n) 没有调用其内部的闭包函数 inner_func(),就返回函数引用,这里我们把函数引用赋值给 f1、f2。
闭包函数的运行代码中,包含了 func(n) 的形参 n,闭包机制自动把 n 的实时值保存到函数引用的 __closure__ 属性。
当 f1 和 f2 被作为函数运行时,f1 和 f2 自动从各自的 __closure__ 属性读取对应的 n 的实时值。
5. Python 装饰器
装饰器(decorator)是 Python 中的一种高级功能,它允许你动态地修改函数或类的行为。Python 装饰器是一种语法糖,本质上是一种函数,它接受一个函数作为参数,并返回一个新的函数或修改原来的函数。
Python 提供了一些内置的装饰器,比如 @property、 @staticmethod 、@classmethod 等。
装饰器的应用场景:
日志记录: 装饰器可用于记录函数的调用信息、参数和返回值。
性能分析: 可以使用装饰器来测量函数的执行时间。
权限控制: 装饰器可用于限制对某些函数的访问权限。
缓存: 装饰器可用于实现函数结果的缓存,以提高性能。
定义/使用装饰器,语法格式如下:
# 定义装饰器 def decorator_function(original_function): def wrapper(*args, **kwargs): # 调用原始函数前添加的新功能 before_call_code() result = original_function(*args, **kwargs) # 调用原始函数后添加的新功能 after_call_code() return result return wrapper # 使用装饰器 @decorator_function def target_function(arg1, arg2): pass # 函数的实现
名词解释:
decorator_function: 自定义的装饰器名称,也是一个装饰器函数。
original_function:装饰器函数的参数,它是一个函数引用(地址)
wrapper:装饰器函数内的一个内部函数,执行一些功能操作,然后调用 original_function,并返回结果。
@decorator_function:@ 符号 + 装饰器名称,表示要把该装饰器运用到 target_function 函数,Python 会自动将 target_function 作为参数传递给装饰器函数 decorator_function
target_function:就是装饰器要装饰的函数。
示例,做一个 time_logger 装饰器,自动给每行 log 前面添加时间戳:
#!/usr/bin/python3 # -*- coding: UTF-8 -*- import time def time_logger(func): def wrapper(*args, **kwargs): l = list(args) if l: l[0] = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) + ' | ' + l[0] result = func(*l, **kwargs) return result return wrapper @time_logger def logger(s): print(s) if __name__ == "__main__": logger('start service') time.sleep(1) logger('stop service')
输出结果如下:
2024-08-20 16:08:23 | start service
2024-08-20 16:08:24 | stop service