|
闭包是指在一个函数内部定义的函数,并且这个内部函数可以访问外部函数的变量、参数
.换句话说,闭包是一个包含了函数及其相关引用环境的组合体.
在Python中,当一个函数返回了内部函数的引用时,这个内部函数可以访问并操作外部函数的局部变量,它就创建了一个闭包, 即使外部函数已经执行完毕,它的局部变量仍然可以被内部函数所使用.
def outer(out_num):
def inner(in_num):
print('out', out_num)
print('in', in_num)
print("他们的和是:", out_num + in_num)
return inner
# 此时f已经持有了inner函数的引用, 形成了闭包.
f = outer(10)
f(5) # 依然可以使用外部函数的局部变量
# 10
# 5
# 15
闭包的主要特点是可以将数据和行为捆绑在一起,
形成一个独立的环境
,而不会与其他部分的代码发生冲突。通过闭包,我们可以实现一些高级的编程技巧,如在函数内部创建私有变量、实现函数工厂或装饰器等.
def outer(out_num):
def inner(in_num):
print("他们的和是:", out_num + in_num)
return inner
# 此时f已经持有了inner函数的引用, 形成了闭包.
f = outer(10)
f(5) # 15
f(15) # 25
print('-' * 20)
f1 = outer(100)
f1(5) # 105
f1(15) # 115
# 每一个闭包实例都持有自己独立的外部函数参数(会消耗一定的内存)
# 并且在内部函数调用的时候都可以准确地计算出结果, 说明闭包在保持状态和数据封装的强大能力.
总结:
- 闭包可以保证数据安全.
- 内层函数对外层函数非全局变量的引用(
只能是局部变量的引用
), 就会形成闭包.- 内部函数可以访问外部函数的变量和参数,即使外部函数已经执行完毕.
- 外部函数的局部变量在闭包中被保存,形成了一个私有的环境.
- 闭包可以捕获和保留外部函数的状态信息,使得函数的状态得以保持和更新.
被引用到的非全局变量也称自由变量, 这个自由变量会与内层函数产生一个绑定关系.
闭包是一种强大的编程技术,它可以使代码更加模块化、可维护和可重用.通过使用闭包,我们可以在函数内部创建和维护状态,而不必依赖全局变量或其他复杂的机制. 这使得我们的代码更加清晰、可靠,并且更容易扩展和修改.
案例1: 幂的计算
def power(exponent):
"""进行幂的计算"""
def calculate_power(base):
return base ** exponent
return calculate_power
# 求平方
# square = power(2)
# num = square(5)
# num1 = square(8)
# num2 = square(11)
# print(num)
# print(num1)
# print(num2)
# 求立方
# cube = power(3)
# num3 = cube(5)
# num4 = cube(6)
# print(num3)
# print(num4)
案例2: 计算每天的平均开销.
def avg():
num_list = []
def inner(num):
num_list.append(num)
print(num_list)
print(sum(num_list) / len(num_list))
return inner
# avg1 = avg()
# avg1(100) # 100
# avg1(200) # 150
# avg1(300) # 200
案例3: 模拟购物车.
def shopping_cart():
# 购物车的清单列表.
items = []
def add_item(item):
items.append(item)
print(f'已添加到购物车: {item}')
def get_items():
return items
return add_item, get_items
usr1_cart, usr1_get = shopping_cart()
usr2_cart, usr2_get = shopping_cart()
# usr1_cart('手机')
# usr1_cart('耳机')
# usr1_cart('相机')
#
# usr2_cart('苹果')
# usr2_cart('橘子')
# usr2_cart('香蕉')
# usr1_items = usr1_get()
# print(usr1_items)
# usr2_items = usr2_get()
# print(usr2_items)
第一个案例和后面两个案例的区别是什么:
区别在于外层函数是否接收参数.
第一个案例中外部函数接受参数, 所以它既可以计算平方也可以计算立方, 甚至可以计算任意次幂.
外部函数接收参数所形成的闭包. 更加的灵活, 可以创建具有不同状态的闭包.
但是外层函数不接收参数的闭包, 它的状态是固定的.
总结: 外部函数是否接收参数决定了闭包的灵活性和定制性.
外部函数中被引用到的非全局变量也称自由变量, 这个自由变量会与内层函数产生一个绑定关系.
自由变量指的是在外部函数中定义的局部变量 被 在内层函数中引用到, 此时这个内层函数中引用到的变量就是自由变量.
def outer(out_num, n):
def inner(in_num):
print("他们的和是:", out_num + in_num)
print(n)
return inner
# 此时f已经持有了inner函数的引用, 形成了闭包.
f = outer(10, 'str') # 创建闭包实例
closure = f.__closure__
print(closure) # 元组
for i in closure:
print(i.cell_contents)
# (, ) | |
# str
# 10
# ---------------------------------------
def outer(out_num):
def inner(in_num):
pass
return inner
# 此时f已经持有了inner函数的引用, 形成了闭包.
f = outer(10) # 创建闭包实例
closure = f.__closure__
print(closure) # None
Python装饰器是一种用于修改函数或类行为的特殊函数
. 它们提供了一种简洁的方式来对现有函数或类进行扩展、修饰或包装,而无需修改它们的源代码. 装饰器通常用于在不影响(不改变)原始函数或类定义的情况下,添加额外的功能或行为.
装饰器本质上是一个函数,它接受一个函数作为输入,并返回一个新的函数
. 装饰器函数通常在内部定义一个闭包函数,用于包装原始函数,添加额外的逻辑或功能. 装饰器函数可以访问原始函数的参数和返回值,并可以在调用前后执行自定义的操作.
装饰器提供了一种简洁而灵活的方式来修改函数的功能,使得我们可以在不改变原代码的情况下添加额外的功能. 用一句话来说就是: 装饰器就是创建一个闭包函数, 在闭包函数的内部调用目标函数, 然后添加相应的功能.
import time
# 计算calculate_sum运行所花费的时间
def cost_time(func):
def inner():
start = time.time()
func()
end_time = time.time()
print(end_time - start_time)
return inner
def calculate_sum():
total = 0
for i in range(1, 100000001):
total += i
print(total)
# 时间戳: 1970.1.1 00:00:00 UTC时间 开始 一直到现在的秒数
calculate_sum = cost_time(calculate_sum)
calculate_sum()
注意比较这行代码和下面使用@符号修饰的装饰器函数:
calculate_sum = cost_time(calculate_sum)装饰器的语法:
在Python中,装饰器是通过使用
@
符号来应用的. 它可以直接放在函数的定义之前,用于修饰该函数.
以下是装饰器的基本语法:
@decorator
def func():
# 函数定义和实现
pass
用Python当中的装饰器改写上面的代码:
import time
def cost_time(func):
def inner():
start_time = time.time()
func()
end_time = time.time()
print(end_time - start_time)
return inner
@cost_time # 等价于 calculate_sum = cost_time(calculate_sum)
def calculate_sum():
total = 0
for i in range(1, 10000001):
total += i
print(total)
calculate_sum()
# print(calculate_sum)发现这个函数指向的是闭包中的inner
@cost_time # 等价于 calculate_sum = cost_time(calculate_sum) def calculate_sum(): ...
上面的 calculate_sum()函数被装饰器函数cost_time修饰后, 此时calculate_sum函数其实就相当于闭包中的inner函数, 通过上面的print(calculate_sum)就可以看出来.
看下面的代码:
import time
def cost_time(func):
def inner():
start_time = time.time()
func()
end_time = time.time()
print(end_time - start_time)
return inner
@cost_time # 等价于 calculate_sum = cost_time(calculate_time)
def calculate_sum():
"""求1-10000000的和"""
total = 0
for i in range(1, 10000001):
total += i
return total
total = calculate_sum() # inner函数没有返回值
print(total) # 这样写肯定是有问题的, 所以怎么改呢?
# -------------------------------------------------
# 很简单, 这样改动即可:
def cost_time(func):
def inner():
start_time = time.time()
result = func()
end_time = time.time()
print(end_time - start_time)
return result
return inner
有一个问题是: 上面是计算固定数字的和, 那我们怎么计算指定数字的和呢?
import time
def cost_time(func):
def inner(num):
start_time = time.time()
result = func(num)
end_time = time.time()
print(end_time - start_time)
return result
return inner
@cost_time # 等价于 calculate_sum = cost_time(calculate_time)
def calculate_sum(num):
"""求1-10000000的和"""
total = 0
for i in range(1, num + 1):
total += i
return total
total = calculate_sum(10000001) # -> 现在这个函数其实就是inner函数
还有一个问题是, 我们想要传入2个参数呢, 或者3个参数呢?
聪明的你肯定能想到使用前面函数教程中所说的 不定长参数.
import time
def cost_time(func):
def inner(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(end_time - start_time)
return result
return inner
@cost_time
def add(a, b):
time.sleep(1)
return a + b
total = add(100000,777)
print(total)
现在思考: 如何给装饰器传递参数呢?
比如上面的装饰器 cost_time 如果想传递参数该怎么做呢?
import time
# 计算 calculate_sum 运行所花费的时间.
def outer(is_need_ret_val):
"""
:param is_need_ret_val: 是否需要返回值, true表示需要, false表示不需要.
:return:
"""
def cost_time(func):
def inner(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
if not is_need_ret_val:
# 如果不需要返回值, 那么就直接打印.
print('函数运行的结果是:', result)
print('函数运行所花费的时间:', end_time - start_time)
else:
return result, end_time - start_time
return inner
return cost_time
@outer(True) # outer()会返回cost_time, 不就和之前的 @cost_time 相同了吗
# @cost_time # 等价于calculate_sum = cost_time(calculate_sum)
def calculate_sum(num):
"""求1-指定数字的和"""
total = 0
for i in range(1, num + 1):
total += i
return total # 实际上面total返回的类型是元组
# 时间戳: 1970.1.1 00:00:00 utc时间 开始 一直到现在的秒数.
# calculate_sum = cost_time(calculate_sum)
total = calculate_sum(10000000)
print(total)
# ---------------------------------------------
# 打印出的结果:
(50000005000000, 0.556506872177124)
注意事项: 在使用装饰器时,需要注意以下几点:
装饰器只会在被装饰函数定义时执行一次
,而不会在每次函数调用时执行.- 装饰器的顺序很重要,多个装饰器的执行顺序是从上到下的.
import time
def cost_time(func):
print("cost_time装饰器正在装饰")
def inner(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print('函数运行所花费的时间:', end_time - start_time)
return result
return inner
@cost_time # 等价于calculate_sum = cost_time(calculate_sum)
def calculate_sum(num):
"""求1-指定数字的和"""
total = 0
for i in range(1, num + 1):
total += i
return total
total = calculate_sum(10000000)
total1 = calculate_sum(5000)
total2 = calculate_sum(4000)
print(total)
print(total1)
print(total2)
# --------------------------------------------
# 执行结果:
cost_time装饰器正在装饰
函数运行所花费的时间: 0.5955467224121094
函数运行所花费的时间: 0.0002911090850830078
函数运行所花费的时间: 0.0002238750457763672
50000005000000
12502500
8002000
由此可见, 装饰器只会在被装饰函数定义时执行.
def decorator1(func):
print("装饰器1正在装饰")
def wrapper():
print("Decorator 1 - Before function execution")
func()
print("Decorator 1 - After function execution")
return wrapper
def decorator2(func):
print("装饰器2正在装饰")
def wrapper():
print("Decorator 2 - Before function execution")
func()
print("Decorator 2 - After function execution")
return wrapper
@decorator1 # my_function = decorator1(my_function) 这里的函数对象是刚才decorator2()返回的结果(先装饰decorator2)
@decorator2 # my_function = decorator2(my_function)
def my_function():
print("Inside my_function")
my_function()
# print(my_function)
# -------------------------------------------------
# 执行结果是:
装饰器2正在装饰
装饰器1正在装饰
Decorator 1 - Before function execution
Decorator 2 - Before function execution
Inside my_function
Decorator 2 - After function execution
Decorator 1 - After function execution
# 注意看上面的执行结果
# 当有多个装饰器装饰的时候
# 装饰的顺序从下到上, 执行的顺序从上到下 - 先装饰, 再执行
装饰器在实际开发中有许多应用场景,以下是一些常见的例子:
练习一: 编写一个装饰器函数, 用于记录函数的调用次数
# 编写一个装饰器函数,用于记录函数的调用次数。
def call_count(func):
count = 0
def wrapper(*args, **kwargs):
nonlocal count
result = func(*args, **kwargs)
count += 1 # 修改了外层的局部变量
print(f'{func.__name__}函数调用了{count}次')
return result
return wrapper
@call_count
def square(num):
print(f"正在计算{num}的平方")
return num ** 2
# square(1)
# square(2)
# square(3)
# square(4)
# square(5)
练习二: 实现一个装饰器函数, 用于缓存函数的执行结果.
# 实现一个装饰器函数, 用于缓存函数的执行结果.
from functools import lru_cache
def outer(func):
cache = {}
def wrapper(*args, **kwargs):
# 定义元组不可变
cache_key = (args, tuple(sorted(kwargs.items()))) # -> ( (), () )
if cache_key in cache:
return cache.get(cache_key)
result = func(*args, **kwargs)
cache[cache_key] = result
return result
return wrapper
@outer
def square(num):
print(f"正在计算{num}的平方")
return num ** 2
a = square(5)
b = square(5)
c = square(6)
d = square(6)
print(a)
print(b)
print(c)
print(d)
# 查看上面的打印结果就可以了
# 其实在Python中是已经实现了这样的装饰器的, 所以可以直接这样:
@call_count
def square(num):
print(f"正在计算{num}的平方")
return num ** 2
根据练习一和练习二有一个问题是, 为什么练习二中没有执行 nonlocal cache, 这是因为练习一中的count = 0是不可变的数据类型, 而cache = {} 是可变的数据类型.
练习三: 创建一个装饰器函数, 使被装饰的函数只能在特定的时间段内执行
比如在早上9点 -> 晚上6点之间, 可以调用, 其余时间不允许调用 -> 所以需要给装饰器传递时间参数 -> 所以需要定义为三层
import datetime
def time_limited(start, end):
def outer(func):
def inner(*args, **kwargs):
now = datetime.datetime.now().time()
if start < now < end:
result = func(*args, **kwargs)
return result
else:
print(f"对不起, 该时间段无法调用此接口.")
return inner
return outer
# @time_limited(start=datetime.time(9, 0), end=datetime.time(18, 0))
@time_limited(start=datetime.time(18, 0), end=datetime.time(23, 59))
def test():
print('我只能在特定的时间段内执行.')
test()
练习四:
某公司要开发一个 系统, 这个系统运行的时候, 用户输入指令,比如,查询价格,就由相应的查询价格的业务代码去处理
假设你是系统架构师, 你确定了如下的代码文件结构
里面包含一个装饰器函数 cmd_dispatch, 这个装饰器函数是给 开发业务工程师使用的。
在这个代码文件中,开发业务代码的工程师,可以像下面这样使用你的装饰器 cmd_dispatch
# ** svc.py **
from lib import cmd_dispatch
@cmd_dispatch('查询价格')
def queryPrice():
# 具体的查询价格处理代码
print('执行查询价格的业务')
@cmd_dispatch('退货')
def refund():
# 具体的退货处理代码
print('执行退货的业务')
装饰器参数是 用户指令字符串
比如,上面的写法就表示,如果用户输入指令 查询价格 , 系统就会调用 queryPrice, 用户输入 退货 , 系统就会调用 refund。
启动代码文件是run.py。
我们运行这个run.py 就启动了整个系统。
代码如下
# ** run.py **
# cmd_route_table 是 lib模块里面的
# 存储了命令对应的业务处理函数
from lib import cmd_route_table
import svc
while True:
op = input('请输入你的操作:')
# 如果能找到该指令的处理函数
if op in cmd_route_table:
# 查找该指令对应的业务处理函数
svcFunc = cmd_route_table[op]
# 调用业务处理函数
svcFunc()
else:
print('该指令没有对应业务处理')
请你写出 lib.py这个库, 完成上述的功能,包括装饰器函数 和 cmd_route_table
## lib.py
cmd_route_table = {}
def cmd_dispatch(cmd):
def inner(func):
cmd_route_table[cmd] = func
return func
return inner
|
|