目录
- 一、函数引用
- 二、回调函数
- 1、定义
- 2、案例
- 三、递归函数
- 1、定义
- 2、特性
- 3、实现过程
- 4、案例
- 四、闭包
- 1、定义
- 2、 构成条件
- 3、闭包的作用
- 4、 闭包的应用
- 5、注意点
- 6、案例
- 7、修改外部函数中的变量
- 五、装饰器
- 1、定义
- 2、功能
- 3、作用
- 4、写法
- 5、案例
- 5.1、被装饰的函数无参数
- 5.2、被装饰的函数有参数
- 5.3、被装饰的函数有不定长参数
- 5.4、多个装饰器
- 5.5、装饰器带参数,在原有装饰器的基础上,设置外部变量
- 5.6、装饰器中的 return
- 六、代码练习
一、函数引用
是指将函数作为对象进行传递、存储和操作的能力。
在 Python 中,函数可以像其它数据类型(如整数、字符串、列表等)一样被传递、赋值、储存和操作。
函数引用允许你将函数名作为一个对象,然后可以在代码中使用这个对象来调用函数。
可以将函数分配给变量,将函数作为参数传递给其它函数,将函数作为返回值等。
引用就是变量指向数据存储空间的现象。
在 Python 中一切都是对象,包括整型数据1,函数,其实是对象。
当我们进行 a=1 的时候,实际上在内存当中有一个地方存了值1,然后用 a 这个变量名存了1所在内存位置的引用。
引用就好像 c 语言里的指针,可以把引用理解成地址。
a 只不过是一个变量名字,a 里面存的是1这个数值所在的地址,就是 a 里面存了数值1的引用。
相同数据使用同一个空间存储,节约内存占用 。
使用 id(数据) 操作可以获取到数据存储的内存空间引用地址。
def test1():
print("--- in test1 func----")
# 调用函数
test1()
# 引用函数
ret = test1
print(id(ret))
print(id(test1))
# 通过引用调用函数
ret()
# 运行结果:
# --- in test1 func----
# 2678016917024
# 2678016917024
# --- in test1 func----
如果函数名后紧跟一对括号,相当于调用这个函数,如果不跟括号,相当于只是一个函数的名字,里面存了函数所在位置的引用。
二、回调函数
1、定义
将函数名字作为参数传递给其它函数去使用。
2、案例
def funa():
print('这是funa')
return '123' # 返回值给到函数名字()
def funb(fn): # fn = funa
print('这是funb')
fn() # fn() = funa()
print(fn()) # print(funa()) 会打印函数的调用
funb(funa) # funa 函数名字作为参数,传递给 funb
# 运行结果:
# 这是funb
# 这是funa
# 这是funa
# 123
三、递归函数
1、定义
如果一个函数在内部不调用其它的函数,而是自己本身的话,这个函数就是递归函数。
2、特性
(1)必须有一个明确的结束条件。
(2)每次进入更深一层递归时,问题规模相比上次递归都应有所减少。
(3)相邻两次重复之间有紧密的联系,前一次要为后一次做准备(通常前一次的输出就作为后一次的输入)。
(4)递归效率不高,递归层次过多会导致栈溢出(在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出)。
3、实现过程
递推:像上边递归实现所拆解,递归每一次都是基于上一次进行下一次的执行,这叫递推。
回溯:则是在遇到终止条件之前,从最后往回返一级一级的把值返回来,这叫回溯。
4、案例
1、计算斐波那契数列
斐波那契数列是一个经典的递归问题。在斐波那契数列中,每个数字是前两个数字的和。用递归函数来计算第 n 个斐波那契数。
def fibonacci(n):
if n <= 1:
return n
else:
return fibonacci(n - 1) + fibonacci(n - 2)
print(fibonacci(5)) # 输出 5,因为第 5 个斐波那契数是 5
2、计算阶乘
阶乘是一个经典的递归问题。阶乘 n! 是前 n 个正整数的乘积。
def factorial(n):
if n == 0 or n == 1:
return 1
else:
return n * factorial(n - 1)
print(factorial(5)) # 输出 120,因为 5! = 5 * 4 * 3 * 2 * 1 = 120
3、求幂
使用递归函数计算一个数的幂。
def power(base, exponent):
if exponent == 0:
return 1
else:
return base * power(base, exponent - 1)
print(power(2, 3)) # 输出 8,因为 2^3 = 2 * 2 * 2 = 8
4、反转字符串
使用递归函数反转一个字符串。
def reverse_string(s):
if len(s) <= 1:
return s
else:
return reverse_string(s[1:]) + s[0]
print(reverse_string("hello")) # 输出 "olleh"
5、查找列表中的最大值
使用递归函数在一个列表中查找最大值。
def find_max(arr):
if len(arr) == 1:
return arr[0]
else:
return max(arr[0], find_max(arr[1:]))
numbers = [3, 8, 1, 5, 9, 2]
print(find_max(numbers)) # 输出 9
注意:
- 递归函数需要确保能够在某些情况下终止,以避免无限循环。这就是为什么基本情况是至关重要的。
- 递归函数可能会引入较大的内存开销,因为每个递归调用都需要在内存中分配新的函数调用堆栈。
- 递归并不总是最高效的解决方案,特别是对于一些问题,迭代或动态规划可能更合适。
四、闭包
1、定义
在函数嵌套的前提下,内部函数使用了外部函数的变量,并且外部函数返回了内部函数,这个使用外部函数变量的内部函数称为闭包。
2、 构成条件
(1)是嵌套在函数中的函数。
(2)必须是内层函数对外层函数的变量(非全局变量)(还包括外部函数的参数)的引用。
(3)外部函数返回了内部函数。
# 闭包
# 嵌套函数
# 内层函数引用外层函数的变量
# 外层函数 返回(return)内层函数(funb)的名字
def funa(a):
print('这是funa---1',a)
def funb(b):
print('这是funb---2',b)
print(a+b)
# return funb # return 属于 funb
return funb # return 属于 funa
# 函数名字 + 括号,才会执行,调用 funb
# 返回值:打印函数的调用
funa(10)(100) # 第一个括号实参,传递给外层函数,第二个括号的,传递给内层函数
# 运行结果:
# 这是funa---1 10
# 这是funb---2 100
# 110
3、闭包的作用
保存局部信息不被销毁,保证数据的安全性。
4、 闭包的应用
(1)可以保存一些非全局变量但是不易被销毁、改变的数据。
(2)装饰器。
5、注意点
由于闭包引用了外部函数的局部变量,则外部函数的局部变量没有及时释放,消耗内存。
6、案例
1、计数器
使用闭包创建一个计数器,用于记录函数被调用的次数。
def counter():
count = 0
def increment():
nonlocal count
count += 1
return count
return increment
counterA = counter()
print(counterA()) # 输出 1
print(counterA()) # 输出 2
2、函数工厂
使用闭包创建一个函数工厂,生成特定功能的函数。
def multiplier(factor):
def multiply(x):
return x * factor
return multiply
double = multiplier(2)
triple = multiplier(3)
print(double(5)) # 输出 10,因为 2 * 5 = 10
print(triple(5)) # 输出 15,因为 3 * 5 = 15
7、修改外部函数中的变量
使用闭包的过程中,一旦外函数被调用一次返回了内函数的引用,虽然每次调用内函数,是开启一个函数执行过后消亡,但是闭包变量实际上只有一份,每次开启内函数都在使用同一份闭包变量。
def outer(a):
def inner(b):
print(a + b)
return inner
ot = outer(1)
ot(2)
# 运行结果:
# 3
nonlocal 关键字只能用于嵌套函数中。
nonlocal 声明的变量只对局部起作用,离开封装函数,那么该变量就无效。
def outer(a): # a 是闭包变量
def inner():
nonlocal a # 内函数中想修改闭包变量
a += 1
print(a)
return inner
ot = outer(1)
ot() # 2
ot() # 3
ot2 = outer(2)
ot2() # 3
ot2() # 4
每次调用 inner 的时候,使用的闭包变量 a 实际上是同一个。
五、装饰器
1、定义
本质上就是一个闭包函数,它可以让其它函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。
2、功能
(1)引入日志
(2)函数执行时间统计
(3)执行函数前预备处理
(4)执行函数后清理功能
(5)权限校验等场景
(6)缓存
3、作用
增强函数的功能,确切的说,可以装饰函数,也可以装饰类。为已经存在的函数或对象添加额外的功能。
4、写法
标准版装饰器
def my_decorator(func):
def wrapper(*args, **kwargs):
# 在调用原始函数之前执行的代码
result = func(*args, **kwargs) # 调用原始函数
# 在调用原始函数之后执行的代码
return result
return wrapper
def say_hello():
print("Hello!")
decorated_hello = my_decorator(say_hello)
decorated_hello() # 调用装饰后的函数
语法糖版装饰器
“语法糖”(Syntactic Sugar)是计算机科学中的一个术语,用于描述一种编程语言功能的设计,这种设计使得代码编写更加方便、易读,但实际上并没有引入新的功能。语法糖可以使代码看起来更加简洁、符合人类的思维习惯,但其背后实际上是对底层原理的一种封装。
def my_decorator(func):
def wrapper(*args, **kwargs):
# 在调用原始函数之前执行的代码
result = func(*args, **kwargs) # 调用原始函数
# 在调用原始函数之后执行的代码
return result
return wrapper
@my_decorator
def say_hello():
print("Hello!")
say_hello() # 调用装饰后的函数
语法糖写法使得装饰器的使用更加简洁,通过在函数定义之前使用
@装饰器函数名
的形式来装饰函数。上述案例中,say_hello 为被装饰函数,my_decorator 为装饰函数。
可以理解为 say_hello 是已经写好的需求,现在要加新的需求,但是并不想改变原来的代码,就把新需求写在 my_decorator 里,然后装饰在 say_hello 上。
5、案例
5.1、被装饰的函数无参数
def funa(fn):
print('这是funa')
def inner():
fn()
print('哈哈哈哈')
return inner
@funa
def test():
print('这是test')
test()
# 运行结果:
# 这是funa
# 这是test
# 哈哈哈哈
5.2、被装饰的函数有参数
def timerun(fn):
def inner(a, b):
print(a, b)
fn(a, b)
return inner
@timerun
def test(a, b):
print('结果是:', a+b)
test(1, 2)
test(2, 3)
# 运行结果:
# 1 2
# 结果是: 3
# 2 3
# 结果是: 5
5.3、被装饰的函数有不定长参数
def funa(fn):
def inner(*args, **kwargs):
print(f'我开始计算{fn.__name__}函数了')
fn(*args, **kwargs)
print('计算完成')
return inner
@funa
def add(a, b):
print(a + b)
add(2, 3)
# 运行结果:
# 我开始计算add函数了
# 5
# 计算完成
5.4、多个装饰器
# 定义函数:完成包裹数据
def maketest1(fn):
def wrapped():
return "哈" + fn() + "哈"
return wrapped
# 定义函数:完成包裹数据
def maketest2(fn):
def wrapped():
return "嘻" + fn() + "嘻"
return wrapped
@maketest1
def test1():
return "hello"
@maketest2
def test2():
return "hello2"
@maketest1
@maketest2
def test3():
return "hello3"
print(test1())
print(test2())
print(test3())
# 运行结果:
# 哈hello哈
# 嘻hello2嘻
# 哈嘻hello3嘻哈
多个装饰器的装饰过程:
离函数最近的装饰器先装饰,然后外面的装饰器再进行装饰,由内到外的装饰过程。
5.5、装饰器带参数,在原有装饰器的基础上,设置外部变量
from time import ctime, sleep
def timefun_arg(pre="hello"):
def timefun(func):
def wrapped_func():
print("%s called at %s %s" % (func.__name__, ctime(), pre))
return func()
return wrapped_func
return timefun
# 下面的装饰过程
# 1. 调用 timefun_arg("itcast")
# 2. 将步骤1得到的返回值,即 time_fun 返回, 然后 time_fun(test1)
# 3. 将 time_fun(test1) 的结果返回,即 wrapped_func
# 4. 让 test1 = wrapped_fun,即 test1 现在指向 wrapped_func
@timefun_arg("itcast")
def test1():
print("I am test1")
@timefun_arg("python")
def test2():
print("I am test2")
test1()
sleep(2)
test1()
test2()
sleep(2)
test2()
# 运行结果:
# test1 called at 当前时间 itcast
# I am test1
# test1 called at 当前时间 itcast
# I am test1
# test2 called at 当前时间 python
# I am test2
# test2 called at 当前时间 python
# I am test2
5.6、装饰器中的 return
一般情况下为了让装饰器更通用,可以有 return。
def timerun(func):
def inner():
print('哈哈')
func()
return inner
@timerun
def test1():
print('study')
@timerun
def test2():
return 'hahaha'
test1()
test2()
print(test2())
# 运行结果:
# 哈哈
# study
# 哈哈
# 哈哈
# None
修改装饰器为 return func()
def timerun(func):
def inner():
print('哈哈')
return func()
return inner
@timerun
def test1():
print('study')
@timerun
def test2():
return 'hahaha'
test1()
test2()
print(test2())
# 运行结果:
# 哈哈
# study
# 哈哈
# 哈哈
# hahaha
六、代码练习
写个装饰器,能够有验证功能,验证 type(1) 函数,循环执行1000000次要花多少时间。
import time
def funa(fn):
def funb():
t1 = time.time() # 函数运行前的时间
for i in range(1000000): # 循环执行 type(1)
fn() # fn() = fun1() 函数名字+括号
t2 = time.time() # 函数运行后的时间
print(f'运行一百万次需要花费:{t2-t1}')
return funb
@funa # @funa 作用执行了:fun1 = funa(fun1) funa() 调用 fn = fun1
def fun1():
type(1) # 执行一百万次要花多少时间
fun1()
# 运行结果:
# 运行一百万次需要花费:0.04616904258728027