Python 笔记 — 回调函数、递归函数、闭包和装饰器

目录

一、函数引用

是指将函数作为对象进行传递、存储和操作的能力。

在 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
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值