赞
踩
这段时间在练习使用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
装饰器的函数不需要实例化,不需要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
装饰器修饰的函数常被定义在类中,被修饰后,不强制要求传递参数(用人能听得懂的说法是该函数不被实例化可以被调用)
#!/usr/bin/python
# -*- coding: UTF-8 -*-
class C(object):
@staticmethod
def f():
print('runoob');
C.f(); # 静态方法无需实例化,输出runoob
cobj = C()
cobj.f() # 也可以实例化后调用,输出runoob
@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
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。