python装饰器本质上是一个什么_理解Python装饰器

装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。

一、装饰器是什么?

装饰器,顾名思义,就是增强函数或类的功能的一个函数。

这么说可能有点绕。

举个例子:如何计算函数的执行时间?

如下,你需要计算 add 函数的执行时间。​​​​​​​

#函数

defadd(a, b):

res= a +breturn res

你可能会这么写​​​​​​​

importtimedefadd(a, b)

start_time=time.time()

res= a +b

exec_time= time.time() -start_timeprint("add函数,花费的时间是:{}".format(exec_time))return res

这个时候,老板又让你计算减法函数(sub)的时间。不用装饰器的话,你又得重复写一段减法的代码。

defsub(a, b)

start_time=time.time()

res= a -b

exec_time= time.time() -start_timeprint("sub函数,花费的时间是:{}".format(exec_time))return res

这样显得很麻烦,也不灵活,万一计算时间的代码有改动,你得每个函数都要改动。

所以,我们需要引入装饰器。

使用装饰器之后的代码是这样的

importtime#定义装饰器

deftime_calc(func):def wrapper(*args, **kargs):

start_time=time.time()

f= func(*args,**kargs)

exec_time= time.time() -start_timereturnfreturnwrapper#使用装饰器

@time_calcdefadd(a, b):return a +b

@time_calcdefsub(a, b):return a - b

是不是看起来清爽多了?

装饰器的作用:增强函数的功能,确切的说,可以装饰函数,也可以装饰类。

装饰器的原理:函数是python的一等公民,函数也是对象。

定义装饰器

defdecorator(func):def wrapper(*args,**kargs):#可以自定义传入的参数

print(func.__name__)#返回传入的方法名参数的调用

return func(*args,**kargs)#返回内层函数函数名

return wrapper

二、使用装饰器

假设decorator是定义好的装饰器。

方法一:不用语法糖@符号​​​​​​​

#装饰器不传入参数时

f =decorator(函数名)#装饰器传入参数时

f = (decorator(参数))(函数名)

方法二:采用语法糖@符号​​​​​​​

#已定义的装饰器

@decoratordeff():pass

#执行被装饰过的函数

f()

装饰器可以传参,也可以不用传参。

自身不传入参数的装饰器(采用两层函数定义装饰器)

deflogin(func):def wrapper(*args,**kargs):print('函数名:%s'% func.__name__)return func(*args,**kargs)returnwrapper

@logindeff():print('inside decorator!')

f()#输出:#>> 函数名:f#>> 函数本身:inside decorator!

自身传入参数的装饰器(采用三层函数定义装饰器)

deflogin(text):defdecorator(func):def wrapper(*args,**kargs):print('%s----%s'%(text, func.__name__))return func(*args,**kargs)returnwrapperreturndecorator#等价于 ==> (login(text))(f) ==> 返回 wrapper

@login('this is a parameter of decorator')deff():print('2019-06-13')#等价于 ==> (login(text))(f)() ==> 调用 wrapper() 并返回 f()

f()#输出:#=> this is a parameter of decorator----f#=> 2019-06-13

functools.wraps

使用装饰器极大地复用了代码,但是他有一个缺点就是原函数的元信息不见了,比如函数的docstring、__name__、参数列表,先看例子:

装饰器

deflogged(func):def with_logging(*args, **kwargs):print(func.__name__ + "was called")return func(*args, **kwargs)return with_logging

函数

@loggeddeff(x):"""does some math"""

return x + x * x

该函数完成等价于:

deff(x):"""does some math"""

return x + x *x

f= logged(f)

不难发现,函数f被with_logging取代了,当然它的docstring,__name__就是变成了with_logging函数的信息了。

print(f.__name__) #prints 'with_logging'

print(f.__doc__) #prints None

这个问题就比较严重的,好在我们有functools.wraps,wraps本身也是一个装饰器,它能把原函数的元信息拷贝到装饰器函数中,这使得装饰器函数也有和原函数一样的元信息了。

from functools importwrapsdeflogged(func):

@wraps(func)def with_logging(*args, **kwargs):print(func.__name__ + "was called")return func(*args, **kwargs)returnwith_logging

@loggeddeff(x):"""does some math"""

return x + x *xprint(f.__name__) #prints 'f'

print(f.__doc__) #prints 'does some math'

装饰器的顺序

@a

@b

@cdef f ():

等效于

f = a(b(c(f)))

三、内置装饰器

常见的内置装饰器有三种,@property、@staticmethod、@classmethod

@property

把类内方法当成属性来使用,必须要有返回值,相当于getter;

假如没有定义 @func.setter 修饰方法的话,就是只读属性

classCar:def __init__(self, name, price):

self._name=name

self._price=price

@propertydefcar_name(self):returnself._name#car_name可以读写的属性

@car_name.setterdefcar_name(self, value):

self._name=value#car_price是只读属性

@propertydefcar_price(self):return str(self._price) + '万'benz= Car('benz', 30)print(benz.car_name) #benz

benz.car_name = "baojun"

print(benz.car_name) #baojun

print(benz.car_price) #30万

@staticmethod

静态方法,不需要表示自身对象的self和自身类的cls参数,就跟使用函数一样。

@classmethod

类方法,不需要self参数,但第一个参数需要是表示自身类的cls参数。

例子

classDemo(object):

text= "三种方法的比较"

definstance_method(self):print("调用实例方法")

@classmethoddefclass_method(cls):print("调用类方法")print("在类方法中 访问类属性 text: {}".format(cls.text))print("在类方法中 调用实例方法 instance_method: {}".format(cls().instance_method()))

@staticmethoddefstatic_method():print("调用静态方法")print("在静态方法中 访问类属性 text: {}".format(Demo.text))print("在静态方法中 调用实例方法 instance_method: {}".format(Demo().instance_method()))if __name__ == "__main__":#实例化对象

d =Demo()#对象可以访问 实例方法、类方法、静态方法

#通过对象访问text属性

print(d.text)#通过对象调用实例方法

d.instance_method()#通过对象调用类方法

d.class_method()#通过对象调用静态方法

d.static_method()#类可以访问类方法、静态方法

#通过类访问text属性

print(Demo.text)#通过类调用类方法

Demo.class_method()#通过类调用静态方法

Demo.static_method()

@staticmethod 和 @classmethod 的 区别 和 使用场景:

在上述例子中,我们可以看出,

区别

在定义静态类方法和类方法时,@staticmethod 装饰的静态方法里面,想要访问类属性或调用实例方法,必须需要把类名写上;

而@classmethod装饰的类方法里面,会传一个cls参数,代表本类,这样就能够避免手写类名的硬编码。

在调用静态方法和类方法时,实际上写法都差不多,一般都是通过 类名.静态方法() 或 类名.类方法()。

也可以用实例化对象去调用静态方法和类方法,但为了和实例方法区分,最好还是用类去调用静态方法和类方法。

使用场景

所以,在定义类的时候,

假如不需要用到与类相关的属性或方法时,就用静态方法@staticmethod;

假如需要用到与类相关的属性或方法,然后又想表明这个方法是整个类通用的,而不是对象特异的,就可以使用类方法@classmethod。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值