装饰器

1、装饰器是什么

装饰器本质是一个函数(函数内会再嵌入一个函数),或者是一个类(类中有__call__()方法),它可以在不对原函数做任何修改的前提下增加额外的功能,装饰器的返回是一个函数或者类对象。

2、为什么使用装饰器

有个函数f(),如果想计算函数f()的执行时间,我们需要定义一个新的函数deco(),它的参数是f(),然后给函数deco()嵌入计时功能。在实际使用中,每个业务函数都需要调用一下deco()函数。如果想要拓展一千万个函数功能,就要执行一千万次deco()函数,并修改deco()函数中的f(),所以这样并不理想。

def deco(func):
    start_time = time.time()
    f()
    end_time = time.time()
    execution_time = (end_time - start_time)*1000
    print("time is %d ms" %execution_time)

def f():
    print("hello")

if __name__ == '__main__':
    deco(f)
3、装饰器的样子
3.1、函数装饰器
import time
def deco(f):
    def wrapper():
        start_time = time.time()
        f()
        end_time = time.time()
        execution_time = (end_time - start_time)*1000
        print("time is %d ms" %execution_time )
    return wrapper
@deco
def f():
    print("hello")

if __name__ == '__main__':
    f()
  1. 这里的deco函数就是最原始的装饰器,它的参数是一个函数,返回值也是一个函数。
  2. @deco用来装饰f()函数,实际上相当于 f = deco(f),可以看出f有了新的引用,即函数deco以f作为参数得到的返回值就是f的新引用
  3. deco()函数的返回值为wrapper的引用,此时相当于f = wrapper,f指向了函数wrapper的引用,并且以f的初始值作为参数
  4. 主函数中调用了f(),也相当于调用了wrapper()方法,在执行wrapper()方法时,以f作为参数
  5. 将计算函数执行时间的函数作为一个装饰器,装饰到需要此功能的函数上,不会影响原功能,也不需要一直修改deco()函数
3.2、类装饰器

类装饰器主要依靠类的__call__方法,当使用@形式将装饰器附加到函数上时,就会调用此方法
可以让类的构造函数__init__()接受一个函数,然后重载__call__()并返回一个函数,也可以达到装饰器函数的效果。

class logging(object):
    def __init__(self, f):
        self.f = f

    def __call__(self, *args, **kwargs):
        print "[DEBUG]: enter function {func}()".format(
        return self.f(*args, **kwargs)
@logging
def say(something):
    print "say {}!".format(something)
4、带参数的装饰器
4.1、带固定参数的装饰器

如果使用装饰器的函数f()有参数,那么需要给装饰器也加上对应的参数,这个参数写在装饰器内层的函数上

def deco(f):
    def wrapper(a,b):
        start_time = time.time()
        f(a,b)
        end_time = time.time()
        execution_time = (end_time - start_time)*1000
        print("time is %d ms" % execution_time)
    return wrapper

@deco
def f(a,b):
    print("hello")

if __name__ == '__main__':
    f(3,4)
4.2、无固定参数的装饰器

之所以无固定参数,原因是:可能有多个函数需要使用同一个装饰器,而每个函数的参数不一致,因此需要装饰器能够满足所有函数的参数形式

def deco(f):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        f(*args, **kwargs)
        end_time = time.time()
        execution_time_ = (end_time - start_time)*1000
        print("time is %d ms" %execution_time)
    return wrapper

@deco
def f(a,b):
    print("be on")
    time.sleep(1)
    print("result is %d" %(a+b))

@deco
def f2(a,b,c):
    print("be on")
    time.sleep(1)
    print("result is %d" %(a+b+c))

if __name__ == '__main__':
    f2(3,4,5)
    f(3,4)

PS:

  1. 在参数名之前使用一个星号def wrapper(*args),就是让函数接受任意多的位置参数,这些参数存储为一个元祖(tuple),显式声明的参数之外如果没有位置参数,这个参数就作为一个空元祖
  2. 在参数名之前使用两个星号def wrapper(**kwargs),就是让函数支持任意多的关键字参数,kwargs是一个正常的python字典类型,包含参数名和值,并且再调用是采取a=1,b=2,c=3的形式。如果没有更多的关键字参数,kwargs就是一个空字典。
5、使用多个装饰器,装饰一个函数

装饰器是可以叠加使用的,那么使用多个装饰器装饰同一个函数时,装饰器的调用顺序与使用 @ 语法糖声明的顺序相反。
下面的例子中:
f(3, 4) = deco01(deco02(f(3, 4)))
先使用离函数近的deco02(f)装饰,装饰后的结果被deco01(f)装饰。

def deco01(f):
    def wrapper(*args, **kwargs):
        print("this is deco01")
        start_time = time.time()
        f(*args, **kwargs)
        end_time = time.time()
        execution_time = (end_time - start_time)*1000
        print("time is %d ms" % execution_time)
    return wrapper

def deco02(f):
    def wrapper(*args, **kwargs):
        print("this is deco02")
        f(*args, **kwargs)
    return wrapper

@deco01
@deco02
def f(a,b):
    print("be on")
    time.sleep(1)
    print("result is %d" %(a+b))

if __name__ == '__main__':
    f(3,4)
6、Python内置装饰器

在Python中有三个内置的装饰器,都是跟class相关的:
staticmethod、classmethod 和property

  1. staticmethod 是类静态方法,其跟成员方法的区别是没有 self 参数,并且可以在类不进行实例化的情况下调用,即可以直接使用类调用,也可以用实例对象调用
  2. classmethod 与成员方法的区别在于所接收的第一个参数不是 self (类实例的指针),而是cls(当前类的具体类型),可以来调用类的属性,类的方法,实例化对象等。
  3. property 是属性的意思,表示可以通过通过类实例直接访问的信息
6.1、classmethod属性的使用原理:
class A(object):
    bar = 1
    def func1(self):  
        print ('foo') 
    @classmethod
    def func2(cls):
        print ('func2')
        print (cls.bar) # 可以调用类的属性
        cls().func1()   # 可以使用cls调用func1()方法
A.func2()               # 不需要实例化,可以直接调用func2()

输出结果为:

func2
1
foo
6.2、property属性的使用原理:

在绑定属性时,如果我们直接把属性暴露出去,虽然写起来很简单,但是,没办法检查参数,导致可以把成绩随便改:
score是一个属性变量

s = Student()
s.score = 9999

为了限制score的范围,可以通过一个set_score()方法来设置成绩,再通过一个get_score()来获取成绩,但每次设置和获取都需要分别调用两个方法,因此可以使用Python内置的@property装饰器,它可以把一个方法变成属性调用,这个方法既能设置,又能获取

class Student(object):
    @property
    def score(self):
        return self._score
    @score.setter
    def score(self, value):
        if not isinstance(value, int):
            raise ValueError('score must be an integer!')
        if value < 0 or value > 100:
            raise ValueError('score must between 0 ~ 100!')
        self._score = value

@property把一个getter方法变成属性,此时,@property本身又创建了另一个装饰器@score.setter,负责把一个setter方法变成属性赋值,于是,我们就拥有一个可控的属性操作。

>>> s = Student()
>>> s.score = 60 # OK,实际转化为s.set_score(60),当作属性比变量使用了
>>> s.score # OK,实际转化为s.get_score()
60
>>> s.score = 9999
Traceback (most recent call last):
  ...
ValueError: score must between 0 ~ 100!

还可以定义只读属性,只定义getter方法,不定义setter方法就是一个只读属性:

class Student(object):
    @property
    def birth(self):
        return self._birth
    @birth.setter
    def birth(self, value):
        self._birth = value
    @property
    def age(self):
        return 2015 - self._birth
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值