这段时间在练习使用python的django框架做个项目,里面经常出现装饰器,刚好之前装饰器的概念又模糊了,在这里写个笔记加固一下印象
什么是装饰器
简单来说装饰器就是用来修改(或者说丰富)函数的函数或者类,是的,装饰器可以是函数也可以是类。python中装饰器用于实现面向切面编程(AOP),以简化,规范代码,类似于Java中的注解(但又有不同)。
python中装饰器的实现得益于一切皆对象的理念,不同于Java,C++,在python中函数也可以像普通变量一样被赋值,当作参数传递。
装饰器的实现可以简单理解为多层函数的嵌套,最内层函数为原本函数,然后依次在外层函数中增加别的功能。以如下代码为例:
def use_logging(func):
def wrapper():
logging.warn("%s is running" % func.__name__)
return func() # 把 foo 当做参数传递进来时,执行func()就相当于执行foo()
return wrapper
def foo():
print('i am foo')
foo = use_logging(foo) # 因为装饰器 use_logging(foo) 返回的时函数对象 wrapper,这条语句相当于 foo = wrapper
foo() # 执行foo()就相当于执行 wrapper()
以上代码用装饰器的语法糖@
来实现就是:
def use_logging(func):
def wrapper():
logging.warn("%s is running" % func.__name__)
return func()
return wrapper
@use_logging
def foo():
print("i am foo")
foo()
使用装饰器可以在不修改原来程序的逻辑,不对原程序做修改的情况下变更原程序的功能,提高了程序的利用率和可读性。
进阶用法
上面提到的装饰器用法只是最简单的,下面将介绍一些更贴近生产环境的用法
给业务逻辑函数传递参数
当业务逻辑函数需要传递参数时,上面实现的简单装饰器就无法满足需求了,这时可以用*args
或者**kwargs
这里简单介绍下
*args
或者**kwargs
的用法;*args
用于指代不确定个数的参数,**kwargs
为指定关键字的不确定个数参数。*args
为列表(list),**kwargs
为字典(dict)
示例代码如下:
def foo(name, age=None, height=None):
print("I am %s, age %s, height %s" % (name, age, height))
给wrapper函数指定参数
def use_logging(func):
def wrapper(*args, **kwargs):
# args是一个列表,kwargs一个字典
logging.warn("%s is running" % func.__name__)
return func(*args, **kwargs)
return wrapper
@use_logging
def foo():
print("i am foo")
foo()
带参数的装饰器
装饰器也可以带参数,上面的装饰器解决了给业务逻辑函数传递参数的问题,带参数的装饰器则进一步丰富了装饰器的功能,比如logging装饰器,可以指定日志记录的等级,代码示例如下:
def use_logging(level): def decorator(func): def wrapper(*args, **kwargs): if level == "warn": logging.warn("%s is running" % func.__name__) elif level == "info": logging.info("%s is running" % func.__name__) return func(*args) return wrapper return decorator @use_logging(level="warn") def foo(name='foo'): print("i am %s" % name) foo()
带参数的装饰器其实时对原装饰器增加了一层函数封装,并返回一个装饰器(装饰器的本质也是函数或类)
类装饰器
前面也提到,装饰器可以是函数也可以是类,类装饰器具有灵活度大,高内聚,封装性等优点。使用类装饰器主要依靠类的__call__
方法,当使用@
形式将装饰器附加导函数上时,就会调用此方法
扫盲时刻:如果一个类Test实现了
__call__
方法,则该类的实例对象test(以test为例)将成为一个可调用对象。可调用对象指的是可以使用运算符()
的对象,允许一个类的实例像函数一样被调用。实质上说,这意味着t()
与t.__call__()
是相同的。同时__call__
参数可变。这意味着你可以定义__call__
为其他你想要的函数,无论有多少个参数。
# -*- coding: utf-8 -*-
class Entity:
"""调用实体来改变实体的位置。"""
def __init__(self, size, x, y):
self.x, self.y = x, y
self.size = size
def __call__(self, x, y):
"""改变实体的位置"""
self.x, self.y = x, y
print('call:', 'x=', self.x, 'y=', self.y)
class Temp:
def __init__(self, x, y):
self.x = x
self.y = y
e = Entity(1, 2, 3) # 创建实例
print(e.x, e.y) # 输出为 2 3
e(4, 5) # 实例可以象函数那样执行,并传入x y值,修改对象的x y;输出 call: x= 4 y= 5
print(callable(e)) # 输出 True
print('***********************')
t = Temp(1, 2)
print(callable(t)) # 输出 False
以上demo演示了__call__
的作用,接下来看看类装饰器的示例(以计数为例)
# -*- coding: utf-8 -*-
class Counter:
def __init__(self, func):
self.func = func
self.count = 0
def __call__(self, *args, **kwargs):
self.count += 1
# 下面的return很重要,因为原函数func有返回值
# 如果去掉return直接执行self.func(*args, **kwargs)
# 会使得原函数丢失返回值
return self.func(*args, **kwargs) #
@Counter
def foo():
return 'hello'
for i in range(10):
foo()
print(foo.count) # 10
首先这里的 @Counter
是装饰器,执行起来顺序是 foo = Counter(foo)
, 实例化,把foo函数传到类Counter里面,并存到对象属性里面,然后返回foo = Counter实例。 这时foo已经是Counter实例,而不是本身foo函数。
当执行foo()的时候,其实已经变成了,执行__call__
函数,在__call__
中会执行self.func()
即foo的实际逻辑,而且加上了计算调用次数,这样就记录了函数的状态
使用装饰器可以极大的提高代码的复用率,但是也存在缺点,就是会丢失原函数的元信息,比如__name__
,docstring
,参数列表等,使用@wraps
装饰器可以解决这个问题,用法如下:
from functools import wraps
def logged(func):
@wraps(func)
def with_logging(*args, **kwargs):
print func.__name__ # 输出 'f'
print func.__doc__ # 输出 'does some math'
return func(*args, **kwargs)
return with_logging
@logged
def f(x):
"""does some math"""
return x + x * x
装饰器顺序
一个函数可以同时使用多个装饰器,如下:
@a
@b
@c
def f ():
pass
它的执行顺序是从里到外,最先调用最里层的装饰器,最后调用最外层的装饰器,它等效于f = a(b(c(f)))
demo示例如下:
def makeBold(fun):
print('----a----')
def inner():
print('----1----')
return '' + fun() + ''
return inner
def makeItalic(fun):
print('----b----')
def inner():
print('----2----')
return '' + fun() + ''
return inner
@makeBold
@makeItalic
def test():
print('----c----')
print('----3----')
return 'hello python decorator'
ret = test()
print(ret)
输出结果为:
----b----
----a----
----1----
----2----
----c----
----3----
hello python decorator
@classmethod装饰器
使用@classmethod
装饰器的函数不需要实例化,不需要self
参数,但第一个参数需要是表示自身类的cls
参数,可以来调用类的属性,类的方法,实例化对象等
class A(object):
# 属性默认为类属性(可以给直接被类本身调用)
num = "类属性"
# 实例化方法(必须实例化类之后才能被调用)
def func1(self): # self : 表示实例化类后的地址id
print("func1")
print(self)
# 类方法(不需要实例化类就可以被类本身调用)
@classmethod
def func2(cls): # cls : 表示没用被实例化的类本身
print("func2")
print(cls)
print(cls.num)
cls().func1()
# 不传递传递默认self参数的方法(该方法也是可以直接被类调用的,但是这样做不标准)
def func3():
print("func3")
print(A.num) # 属性是可以直接用类本身调用的
# A.func1() 这样调用是会报错:因为func1()调用时需要默认传递实例化类后的地址id参数,如果不实例化类是无法调用的
A.func2()
A.func3()
@staticmethod装饰器
被@staticmethod
装饰器修饰的函数常被定义在类中,被修饰后,不强制要求传递参数(用人能听得懂的说法是该函数不被实例化可以被调用)
#!/usr/bin/python
# -*- coding: UTF-8 -*-
class C(object):
@staticmethod
def f():
print('runoob');
C.f(); # 静态方法无需实例化,输出runoob
cobj = C()
cobj.f() # 也可以实例化后调用,输出runoob
@proterty装饰器
@property
装饰器会将方法转换为相同名称的只读属性(修饰方法,使方法可以像属性一样被访问);还可以与所定义的属性配合使用,可以防止属性被修改
示例1:
# -*- coding: utf-8 -*-
class DataSet(object):
@property
def method_with_property(self): # 含有@property
return 15
def method_without_property(self): # 不含@property
return 15
demo = DataSet()
print(demo.method_with_property) # 输出15,@property后,可以用调用属性的形式来调用方法,后面不需要加`()`。
print(demo.method_without_property()) # 没有加@property , 必须使用正常的调用方法的形式,即在后面加`()`
print(demo.method_without_property) # 没有加@property,直接调用会输出该方法存放的地址
print(demo.method_with_property())
# 如果使用property进行修饰后,又在调用的时候,方法后面添加了`()`
# 那么就会显示错误信息:TypeError: 'int' object is not callable,
# 也就是说添加@property 后,这个方法就变成了一个属性,如果后面加入了()
# 那么就是当作函数来调用,而它却不是callable(可调用)的。
由于python进行属性的定义时,没办法设置私有属性,因此要通过@property的方法来进行设置。这样可以隐藏属性名,让用户进行使用的时候无法随意修改。
示例2:
# -*- coding: utf-8 -*-
class DataSet(object):
def __init__(self):
self._images = 1
self._labels = 2 # 定义属性的名称
@property # 使得images可读
def images(self): # 方法加入@property后,这个方法相当于一个属性,这个属性可以让用户进行使用,而且用户有没办法随意修改。
return self._images
@property # 使得images可写
def labels(self):
return self._labels
@labels.setter # 使得images可删除
def labels(self, value):
self._labels = value
@labels.deleter
def labels(self):
del self._labels
demo = DataSet()
# 用户进行属性调用的时候,直接调用images即可,而不用知道属性名_images,因此用户无法更改属性,从而保护了类的属性。
print(demo.images) # 输出1,加了@property后,可以用调用属性的形式来调用方法,后面不需要加()。
print(demo.labels) # 输出2
demo.labels = 3
print(demo.labels) # 输出3
del demo.labels # 删除属性_labels
print(demo.labels) # 报错:AttributeError: 'DataSet' object has no attribute '_labels'
del demo.images # 因为上一句报错,这句不会执行到,如果执行到的话,会报错:AttributeError: can't delete attribute
参考:https://www.runoob.com/w3cnote/python-func-decorators.html
参考:https://www.cnblogs.com/slysky/p/9777424.html