Day 26:Python 中的装饰器是什么?
Python 中使用 @函数名字
,放在某些函数上面,起到增强它们功能的作用。
- 一切皆对象
- 在函数中定义函数
- 从函数中返回函数(既然可以创建嵌套的函数。那函数也能返回函数)
def hi(name="piayie"): def greet(): return "now you are in the greet() function" def welcome(): return "now you are in the welcome() function" if name == "pieyie": return greet else: return welcome a = hi() print(a) output: <function hi.<locals>.greet at 0x6fff96c98c8>
注意返回的是 greet 和 welcome,而不是 greet() 和 welcome()。这是因为当你把一对小括号放在后面,这个函数就会执行;然而如果你不放括号在它后面,那它可以被到处传递,并且可以赋值给别的变量而不去执行它。
- 还可以做到将函数作为参数传给另一个函数
def hi(): return "hi piayie!" def doSomethingBeforeHi(func): print("I am doing some boring work before executing hi()") print(func()) doSomethingBeforeHi(hi) output: I am doing some boring work before executing hi() hi piayie!
增强函数功能,方便重复调用,但是函数本身的功能的执行也是需要的,并不是说把函数直接变成了另一个函数
装饰器有助于让我们的代码更简短,也更Pythonic。
会使得代码更加简洁,代码的复用性大大提升。
因此,装饰器被广泛使用。Python 中到处都能看到装饰器的身影。
不光在 Python,其他语言如 Java 中,装饰器被称作注解,也被应用广泛。
Python 前端最流行的框架之一 Flask,URL 地址和 controller 层的映射处理函数,就是使用装饰器。
# import Flask,同时创建一个 app: from flask import Flask app = Flask(__name__) #URL 路径 / 与处理函数建立映射关系,正是通过装饰器 app.route 控制层处理响应前端,并返回字符串 hello world 到前端界面。 @app.route('/') def index(): return "hello world" # Python 支持异步编程,从中也能看到装饰器的身影 # 使用装饰器 @asyncio.coroutine,将一个生成器asyncio_open_conn 标记为 coroutine 类型 import asyncio @asyncio.coroutine def asyncio_open_conn(host:str): print("using asyncio builds web connection") connect = yield from asyncio.open_connection(host, 80) print("get connect of %s"%(host,)) loop = asyncio.get_event_loop() connections = [asyncio_open_conn(host) for host in [ 'www.sina.com', 'www.baidu.com']] loop.run_until_complete(asyncio.wait(connections)) loop.close() output: using asyncio builds web connection using asyncio builds web connection get connect of www.sina.com get connect of www.baidu.com
下面介绍几个装饰器例子来理解什么是装饰器
call_print 装饰器
# 定义函数 call_print,它的入参 f 为一个函数,它里面内嵌一个函数 g,并返回函数 g def call_print(f): def g(): print('you\'re calling %s function' % (f.__name__,)) return g
- Python 中,@call_print 函数,放在别的🐍么函数上面,函数 call_print 就变为装饰器。
- 变为装饰器后,我们不必自己去调用函数 call_print。
@call_print def myfun(): pass @call_print def myfun2(): pass
直接调用被装饰的🐍么函数,就能调用到 call_print,观察输出结果:
# 定义函数 call_print,它的入参 f 为一个函数,它里面内嵌一个函数 g,并返回函数 g def call_print(f): def g(): print('you\'re calling %s function' % (f.__name__,)) return g def my_chain(*iterables): for it in iterables: for element in it: yield element @call_print def myfun(): chain_it = my_chain(['I','love'],['Flower Dance'],['very', 'much',['very', 'much']]) for _ in chain_it: print(_) @call_print def myfun2(): pass myfun() myfun2() output: you're calling myfun function you're calling myfun2 function
- @call_print 放置在任何一个新定义的函数上面。都会默认输出一行,输出信息:正在调用这个函数的名称。
@call_print 放新定义函数的上面实现了这样的调用:
myfun = call_print(myfun)
myfun2 = call_print(myfun2)
call_print(myfun) 后返回一个函数,然后,我们再将它赋值给被传入的函数 myfun。
myfun 指向了被包装后的函数 g,并移花接木到函数 g,使得 myfun 额外具备了函数 g 的一切功能,变得更强大。
wraps 装饰器
再次打印myfun()和myfun
myfun() myfun2() print(myfun) print(myfun2) output: you're calling myfun function you're calling myfun2 function <function call_print.<locals>.g at 0x6fffdd2af28> <function call_print.<locals>.g at 0x6fffdd2ad08>
被装饰的函数 myfun 名字竟然变为 g,也就是定义装饰器 call_print 时使用的内嵌函数 g
而且函数myfun本身的函数却并没有被执行,就像是完全被替换了一个函数,看:
from functools import wraps def call_print(f): @wraps(f) def g(): print('you\'re calling %s function' % (f.__name__,)) return g def my_chain(*iterables): for it in iterables: for element in it: yield element # @call_print def myfun(): chain_it = my_chain(['I','love'],['Flower Dance'],['very', 'much',['very', 'much']]) for _ in chain_it: print(_) @call_print def myfun2(): chain_it = my_chain(['I','love'],['Last Dance'],['very', 'much',['very', 'much']]) for _ in chain_it: print(_) myfun() myfun2() print(myfun) print(myfun2) output: I love Flower Dance very much ['very', 'much'] you're calling myfun2 function <function myfun at 0x6fffdd089d8> <function myfun2 at 0x6fffdd08ae8>
现在函数名倒是正常了,可原先myfun2()的函数体还是没有执行???!😱
补充说明
并不是完全替换,要看装饰器函数中要不要执行原先的函数,有没有把函数当作参数传进去并且再里面调用:
def a_new_decorator(a_func): def wrapTheFunction(): print("I am doing some boring work before executing a_func()") a_func() print("I am doing some boring work after executing a_func()") return wrapTheFunction def a_function_requiring_decoration(): print("I am the function which needs some decoration to remove my foul smell") a_function_requiring_decoration() output: I am the function which needs some decoration to remove my foul smell
但是如果这样写:(这实际上是@a_new_decorator做的事情)
# 把函数a_function_requiring_decoration当作参数传给函数a_new_decorator a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration)
输出就会大不一样:
a_function_requiring_decoration()
output:
I am doing some boring work before executing a_func()
I am the function which needs some decoration to remove my foul smell
I am doing some boring work after executing a_func()
现在尝试一下使用@符号达到上面的效果
def a_new_decorator(a_func): def wrapTheFunction(): print("I am doing some boring work before executing a_func()") a_func() print("I am doing some boring work after executing a_func()") return wrapTheFunction @a_new_decorator def a_function_requiring_decoration(): print("I am the function which needs some decoration to remove my foul smell") a_function_requiring_decoration() output: I am doing some boring work before executing a_func() I am the function which needs some decoration to remove my foul smell I am doing some boring work after executing a_func()
现在试一下执行myfun里面的函数
from functools import wraps def call_print(f): @wraps(f) def g(): print('you\'re calling %s function' % (f.__name__,)) print('and I begin to execute the function f who is a parameter for call_print') f() print('Now I done!') return g def my_chain(*iterables): for it in iterables: for element in it: yield element @call_print def myfun(): chain_it = my_chain(['I','love'],['Flower Dance'],['very', 'much']) for _ in chain_it: print(_) @call_print def myfun2(): chain_it = my_chain(['I','love'],['Last Dance'],['very', 'much']) for _ in chain_it: print(_) myfun() myfun2() print(myfun) print(myfun2) output: you're calling myfun function and I begin to execute the function f who is a parameter for call_print I love Flower Dance very much Now I done! you're calling myfun2 function and I begin to execute the function f who is a parameter for call_print I love Last Dance very much Now I done! <function myfun at 0x6fffdf15e18> <function myfun2 at 0x6fff35ac048>
学fei了吗?啊🥱
案例 1:异常次数
import time import math def excepter(f): i = 0 t1 = time.time() def wrapper(): try: f() except Exception as e: nonlocal i i += 1 print(f'{e.args[0]}: {i}') t2 = time.time() if i == n: print(f'spending time:{round(t2-t1,2)}') return wrapper
使用创建的装饰函数 excepter,n 是异常出现的次数。
- 被零除的情况显然是要报错的,然后没有执行那块代码,去泡装饰器函数@excepter了
- 数组显然是越界了,但是没有报错,同理
n = 10# except count @excepter def divide_zero_except(): time.sleep(0.1) j = 1/(40-20*2) # test zero divived except for _ in range(n): divide_zero_except() output: division by zero: 1 division by zero: 2 division by zero: 3 division by zero: 4 division by zero: 5 division by zero: 6 division by zero: 7 division by zero: 8 division by zero: 9 division by zero: 10 spending time:1.08
@excepter def outof_range_except(): a = [1,3,5] time.sleep(0.1) print(a[3]) # test out of range except for _ in range(n): outof_range_except() output: list index out of range: 1 list index out of range: 2 list index out of range: 3 list index out of range: 4 list index out of range: 5 list index out of range: 6 list index out of range: 7 list index out of range: 8 list index out of range: 9 list index out of range: 10 spending time:1.1
案例 2:绘图
import numpy as np from scipy.stats import beta, norm, uniform, binom import matplotlib.pyplot as plt from functools import wraps # 定义带四个参数的画图装饰器 # 使用 wraps,包装了内嵌函数 myplot。这样做的好处,被包装的函数名字不会被改变 # 绘图装饰器带有四个参数分别表示,legend 的两类说明文字,y 轴 label,保存的 PNG 文件名称。 def my_plot(label0=None, label1=None, ylabel='probability density function', fn=None): def decorate(f): @wraps(f) def myplot(): fig = plt.figure(figsize=(16, 9)) ax = fig.add_subplot(111) x, y, y1 = f() ax.plot(x, y, linewidth=2, c='r', label=label0) ax.plot(x, y1, linewidth=2, c='b', label=label1) ax.legend() plt.ylabel(ylabel) # plt.show() plt.savefig('./img/%s' % (fn,)) plt.close() return myplot return decorate # 均匀分布 @my_plot(label0='b-a=1.0', label1='b-a=2.0', fn='uniform.png') def unif(): x = np.arange(-0.01, 2.01, 0.01) y = uniform.pdf(x, loc=0.0, scale=1.0) y1 = uniform.pdf(x, loc=0.0, scale=2.0) return x, y, y1 # 二项分布 @my_plot(label0='n=50,p=0.3', label1='n=50,p=0.7', fn='binom.png', ylabel='probability mass function') def bino(): x = np.arange(50) n, p, p1 = 50, 0.3, 0.7 y = binom.pmf(x, n=n, p=p) y1 = binom.pmf(x, n=n, p=p1) return x, y, y1 # 高斯分布 @my_plot(label0='u=0.,sigma=1.0', label1='u=0.,sigma=2.0', fn='guass.png') def guass(): x = np.arange(-5, 5, 0.1) y = norm.pdf(x, loc=0.0, scale=1.0) y1 = norm.pdf(x, loc=0., scale=2.0) return x, y, y1 # beta 分布 @my_plot(label0='a=10., b=30.', label1='a=4., b=4.', fn='beta.png') def bet(): x = np.arange(-0.1, 1, 0.001) y = beta.pdf(x, a=10., b=30.) y1 = beta.pdf(x, a=4., b=4.) return x, y, y1 # 调用 distrs = [unif, bino, guass, bet] for distri in distrs: distri()
案例三:@classmethod和@staticmethod
移除元素:27. 移除元素 - 力扣(LeetCode) (leetcode-cn.com)
python双指针解法:
from typing import List class movValSolution: """双指针法 时间复杂度:O(n) 空间复杂度:O(1) """ @classmethod def removeElement(cls, nums: List[int], val: int) -> int: fast = slow = 0 while fast < len(nums): if nums[fast] != val: nums[slow] = nums[fast] slow += 1 # 当 fast 指针遇到要删除的元素时停止赋值 # slow 指针停止移动, fast 指针继续前进 fast += 1 return slow
细心的张同学发现俩个不太熟悉的东西
- @classmethod
- def removeElement(cls,
深入浅析python 中的self和cls的区别 - 云+社区 - 腾讯云 (tencent.com)
描述:
self是类(Class)实例化对象,cls就是类(或子类)本身,取决于调用的是那个类。
@staticmethod 属于静态方法装饰器,@classmethod属于类方法装饰器。我们需要从声明和使用两个方面来理解。
即我要么通过类的实例来调用类中的函数,要么直接通过类的方法调用那个函数
1 2 3 | a = movValSolution() print (a.removeElement([ 1 , 2 , 3 , 4 , 5 , 6 , 3 , 2 , 1 , 4 , 3 , 3 , 3 ], 3 )) print (movValSolution.removeElement([ 1 , 2 , 3 , 4 , 5 , 6 , 3 , 2 , 1 , 4 , 3 , 3 , 3 ], 3 )) |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~