python装饰器

装饰器的出现是广大**的福音, 可以无需修改原有代码结构,直接运行该代码函数,而获取该函数相关信息,如name,doc,run time等一系列信息,如下带你进入装饰器的世界.

1 入门

1.0 不带参数装饰器

【Demo】

from functools import wraps
def trace(func):
	# @wraps(func)
    def callf(*args, **kwargs):
        '''A wrapper function.'''
        print("Calling function: {}".format(func.__name__))
        res = func(*args, **kwargs)
        print("Return value: {}".format(res))
        return res
    return callf
    
@trace
def foo(x):
    '''Return value square.'''
    return x*x
if __name__ == "__main__":
    print(foo(3))
    print(foo.__doc__)# A wrapper function.
    print(foo.__name__)# callf
  • Result
Calling function: foo
Return value: 9
9
A wrapper function.
callf

【Analysis】
(1) python3.x中使用@调用函数作为装饰器,其中trace为装饰器函数名,调用装饰器和调用其他函数一样,执行函数功能,该trace函数返回的是callf函数,因此执行callf函数功能;
(2) 调用foo(3)即先执行callf函数,函数foo就是callf中的func函数,所以第一个结果为Callling function:foo,由此可知func为函数foo',第二个结果为Return value: 9,第三个结果为foo函数功能,输出9.
(3) foo函数为装饰器函数,由foo.__name__结果为callf,装饰器信息丢失,foo.__doc__结果A wrapper function可知.
(4) 使用wrapper(func)可避免抓装饰器信息丢失.

@trace 
def foo(x)
等价于:
trace(foo)

1.2 带参数装饰器

【Demo】

class logging(object):
    def __init__(self, level="INFO"):
        self.level = level
        
    def __call__(self, func):
        def wrapper(*args, **kwargs):
            print("[{level}]: enter function {func}()".format(level=self.level, func=func.__name__))
            func(*args, **kwargs)
#             return func(*args, **kwargs)
        return wrapper
@logging(level="INOF")
def say(x):
    print("hello {}".format(x))
if __name__ == "__main__":
    say("hahaha")

【Result】

[INOF]: enter function say()
hello hahaha

【Analysis】
(1) 原理同上,只是带参数的装饰器使用类(class)的初始化提供参数输入.
(2) 通过对类中的函数进行重写,如init初始化函数, − − c a l l − − _{--}call_{--} call调用函数,实现参数及函数使用.

2 提高

2.1 计时装饰器

【普通函数】plot_sin

def plot_sin():
    PI = np.pi
    x = np.linspace(-2*PI, 2*PI, 100)
    y = np.sin(x)
    plt.figure(figsize=(6, 6))
    plt.grid()
    plt.plot(x, y, 'r-', label="$y=sin(x)$")
    plt.legend()
    plt.show()

对于某一个函数,可能是自己写的,也可能不是自己写的,但是需要获取程序运行时间,发现函数是一个月前甚至更久之前写的,惊不惊喜,意不意外,此时装饰器即可解决问题,无需修改源码,直接写装饰器函数即可,代码如下:

import numpy as np
import matplotlib.pyplot as plt
import time
 
def time_cal(func):
    def wrapper(*args, **kwargs):
        time_start = time.time()
        func()
        time_end = time.time()
        time_cost = time_end - time_start
        return time_cost
    return wrapper

@time_cal
def plot_sin():
    PI = np.pi
    x = np.linspace(-2*PI, 2*PI, 100)
    y = np.sin(x)
    plt.figure(figsize=(6, 6))
    plt.grid()
    plt.plot(x, y, 'r-', label="$y=sin(x)$")
    plt.legend()
    plt.show()
    
if __name__ == "__main__":
    print("plot_sin function cost time: {}s".format(plot_sin()))

【Result】

plot_sin function cost time: 0.1657559871673584s

【Analysis】
(1) 已知函数plot_sin欲测试函数运行时间,但是对函数不熟悉,可直接使用装饰器,不改变函数结构,在装饰器中实现时间测试的功能.
(2) 无需修改plot_sin函数结构,直接在装饰器函数下面即可.

functools
闭包
装饰器接口

2.2 wraps

【Demo】

from functools import wraps
def trace(func):
    @wraps(func)
    def callf(*args, **kwargs):
        '''A wrapper function.'''
        print("Calling function: {}".format(func.__name__))
        res = func(*args, **kwargs)
        print("Return value: {}".format(res))
        return res
    return callf
    
@trace
def foo(x):
    '''Return value square.'''
    return x*x
if __name__ == "__main__":
    print(foo(3))
    print(foo.__doc__)
    print(foo.__name__)

【Result】

Calling function: foo
Return value: 9
9
Return value square.
foo

【Analysis】
(1) 使用wraps可避免装饰器信息丢失.
(2) foo.__name__输出为函数名foo.

2.3 装饰器链

【Demo】

def wrapper_1(func):
    return lambda: "w1 "+func()+"w1 "
def wrapper_2(func):
    return lambda:"w2"+func()+"w2 "
@wrapper_1
@wrapper_2
def foo():
    return " hahah "
if __name__ == "__main__":
    print(foo())

【Result】

w1 w2 hahah w2 w1 

【Analysis】
(1) 一个函数同时可由多个装饰器使用,组成装饰器链;
(2) 执行顺序是由近及远,w1 w2 hahah w2 w1,先执行wrapper_2在执行wrapper_1.

2.4 内置装饰器

2.4.1 @property

功能:将类的方法转换为同名称的属性,完成对属性的增删改,其中:
__intit__增加属性;
@property获取属性;
@x.setter修改属性;
@x.deleter删除属性;

【Demo1】

class Foo(object):
    def __init__(self):
        self._x = None
    @property
    def x(self):
        '''Get value.'''
        print("get")
        return self._x
    @x.setter
    def x(self, value):
        '''Set value.'''
        print("set")
        if value > 10:
            raise ValueError("invalid value")
        self._x = value
    @x.deleter
    def x(self):
        '''Delete value.'''
        print("delete attribute")
        del self._x
        
if __name__ == "__main__":
    foo = Foo()
    print(help(foo))
    '''set'''
    foo.x = 1
    '''get'''
    print("x value: {}".format(foo.x))
    '''delete'''
    del foo.x
    '''get'''
    print("x value: {}".format(foo.x))

【Result】

Help on Foo in module __main__ object:

class Foo(builtins.object)
 |  Methods defined here:
 |  
 |  __init__(self)
 |      Initialize self.  See help(type(self)) for accurate signature.
 |  
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |  
 |  __dict__
 |      dictionary for instance variables (if defined)
 |  
 |  __weakref__
 |      list of weak references to the object (if defined)
 |  
 |  x
 |      Get value.

None
set
get
x value: 1
delete attribute
get

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-153-276bb444891e> in <module>
     30     del foo.x
     31     '''get'''
---> 32     print("x value: {}".format(foo.x))
     33 

<ipython-input-153-276bb444891e> in x(self)
      6         '''Get value.'''
      7         print("get")
----> 8         return self._x
      9     @x.setter
     10     def x(self, value):

AttributeError: 'Foo' object has no attribute '_x'

【Analysis】
(1) help(instantiation)即help(foo)输出类架构.
(2) @property有四个参数,fget, fset, fdel, doc,其中:

序号参数描述
1fget获取属性值
2fset设置属性值
3fdel删除属性
4doc属性描述

(3) @property默认属性为get,之后的二个为get属性的衍生,通过转换后的属性执行set,del.
(4) 执行del后,即删除创建的属性,因此不能获取该属性的值,所以报错AttributeError: 'Foo' object has no attribute '_x'.

【Demo2】

class Foo(object):
    def __init__(self, name, score):
        self.name = name
        self._score = score
    @property
    def score(self):
        return self._score
    @score.setter
    def score(self, value):
        if value > 100:
            raise ValueError('invalid score')
        self.__score = value
    @score.deleter
    def score(self):
        del self._score

if __name__ == "__main__":            
            
    foo = Foo("hahah", 100)
    print(foo.score)
    foo.score = 1000
    foo.score
    foo.score

【Reuslt】

100
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-142-99037b9e42d8> in <module>
     19     foo = Foo("hahah", 100)
     20     print(foo.score)
---> 21     foo.score = 1000
     22     foo.score
     23     foo.score

<ipython-input-142-99037b9e42d8> in score(self, value)
      9     def score(self, value):
     10         if value > 100:
---> 11             raise ValueError('invalid score')
     12         self.__score = value
     13     @score.deleter

ValueError: invalid score

【Analysis】
(1) 通过装饰器@property可直接通过类的方法修改的私有属性__score;
(2) s.score替代s.__score.
(3) 通过setter设定取值范围,限制取值,超出范围报错;

2.4.2 classmethod

使用@classmethod将类作为函数的第一参数,替代self传递当前类实例化对象.
【Demo1】

class Test(object):
    def foo(self, x):
        print("executing foo self:{}, x: {}".format(self, x))
    @classmethod
    def class_foo(cls, x):
        print("executing class_foo cls: {}, x: {}".format(cls, x))
    @staticmethod
    def static_foo(x):
        print("executing static_foo: {}".format(x))

t = Test()
t.foo(250)
t.class_foo(250)
t.static_foo(250)
Test.foo(t, 250)
Test.class_foo(250)
Test.static_foo(250)

【Result】

executing foo self:<__main__.Test object at 0x7fac7bfee518>, x: 250
executing class_foo cls: <class '__main__.Test'>, x: 250
executing static_foo: 250
executing foo self:<__main__.Test object at 0x7fac7bfee518>, x: 250
executing class_foo cls: <class '__main__.Test'>, x: 250
executing static_foo: 250

【Analysis】
(1) self返回的是类Test的对象,cls返回的是类Test;
(2) 使用@classmethod定义的函数可以通过类直接调用,无需实例化;
(3) self传递的是类实例化的对象,cls传递的是类,因为实例化t=Test()调用普通函数形式为:t.foo,通过实例t调用函数,若使用类调用函数,需要将实例化的对象传递给self,如:Test.foo(t, 250);cls通过类调用函数:Test.class_foo;

【Demo2】

class DateOut(object):
    def __init__(self, year=0, month=0, day=0):
        self.year = year
        self.month = month
        self.day = day
    @classmethod
    def get_date(cls, data):
        year, month, day = map(int, data.split("-"))
        date = cls(year, month, day)
        return date
    def output_date(self):
        print("date: {}Y {}M {}D".format(self.year, self.month, self.day))
date = DateOut.get_date("2019-4-28")
print("date: {}".format(date))
date.output_date()

【Result】

date: <__main__.DateOut object at 0x7fac7bff3978>
date: 2019Y 4M 28D

3 总结

(1) 装饰器分为自定义装饰器和内置装饰器;
(2) 自定义装饰器形成函数闭包,可以不修改函数获取函数名称,运行时间;
(3) 内置装饰器常用的有@preperty和@classmethod;
(4) @property装饰器可将函数作为同名的属性使用,完成增删改查;
(5) @classmethod装饰器可将类作为第一个参数传给类中的函数,这样可以实现在类的函数中使用类的初始化功能;
(6) 类函数的第一个形参必须为类的实例,用self表示类的实例化对象,作为第一个形参,该参数约定俗成用self,也可以使用其他命名,为了提高程序可读性,公认使用self;
(7) @staticmethod不用实例化,即可使用下面的方法,用法与@classmethod相同。


[参考文献]
[1]https://www.cnblogs.com/Neeo/p/8371826.html
[2]https://www.cnblogs.com/cicaday/p/python-decorator.html
[3]https://www.cnblogs.com/huchong/p/7725564.html
[4]https://www.cnblogs.com/lianyingteng/p/7743876.html
[5]https://blog.csdn.net/kangkanglou/article/details/79076357
[6]https://docs.python.org/3/library/functions.html#property
[7]https://www.cnblogs.com/elie/p/5876210.html
[8]https://blog.csdn.net/test_xhz/article/details/82024838
[9]https://www.cnblogs.com/jessonluo/p/4717140.html
[10]http://www.cnblogs.com/chownjy/p/8663024.html


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值