PHP前端开发

详解Python的装饰器

百变鹏仔 4小时前 #Python
文章标签 详解

python中的装饰器是你进入python大门的一道坎,不管你跨不跨过去它都在那里。

为什么需要装饰器

我们假设你的程序实现了say_hello()和say_goodbye()两个函数。

def say_hello():     print "hello!"      def say_goodbye():     print "hello!"  # bug here  if __name__ == '__main__':     say_hello()     say_goodbye()

但是在实际调用中,我们发现程序出错了,上面的代码打印了两个hello。经过调试你发现是say_goodbye()出错了。老板要求调用每个方法前都要记录进入函数的名称,比如这样:

[DEBUG]: Enter say_hello() Hello! [DEBUG]: Enter say_goodbye() Goodbye!

好,小A是个毕业生,他是这样实现的。

立即学习“Python免费学习笔记(深入)”;

def say_hello():     print "[DEBUG]: enter say_hello()"     print "hello!"  def say_goodbye():     print "[DEBUG]: enter say_goodbye()"     print "hello!"  if __name__ == '__main__':     say_hello()     say_goodbye()

很low吧? 嗯是的。小B工作有一段时间了,他告诉小A可以这样写。

def debug():     import inspect     caller_name = inspect.stack()[1][3]     print "[DEBUG]: enter {}()".format(caller_name)     def say_hello():     debug()     print "hello!"  def say_goodbye():     debug()     print "goodbye!"  if __name__ == '__main__':     say_hello()     say_goodbye()

是不是好一点?那当然,但是每个业务函数里都要调用一下debug()函数,是不是很难受?万一老板说say相关的函数不用debug,do相关的才需要呢?

那么装饰器这时候应该登场了。

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

概括的讲,装饰器的作用就是为已经存在的函数或对象添加额外的功能。

怎么写一个装饰器

在早些时候 (Python Version

def debug(func):     def wrapper():         print "[DEBUG]: enter {}()".format(func.__name__)         return func()     return wrapper  def say_hello():     print "hello!"  say_hello = debug(say_hello)  # 添加功能并保持原函数名不变

上面的debug函数其实已经是一个装饰器了,它对原函数做了包装并返回了另外一个函数,额外添加了一些功能。因为这样写实在不太优雅,在后面版本的Python中支持了@语法糖,下面代码等同于早期的写法。

def debug(func):     def wrapper():         print "[DEBUG]: enter {}()".format(func.__name__)         return func()     return wrapper  @debug def say_hello():     print "hello!"

这是最简单的装饰器,但是有一个问题,如果被装饰的函数需要传入参数,那么这个装饰器就坏了。因为返回的函数并不能接受参数,你可以指定装饰器函数wrapper接受和原函数一样的参数,比如:

def debug(func):     def wrapper(something):  # 指定一毛一样的参数         print "[DEBUG]: enter {}()".format(func.__name__)         return func(something)     return wrapper  # 返回包装过函数  @debug def say(something):     print "hello {}!".format(something)

这样你就解决了一个问题,但又多了N个问题。因为函数有千千万,你只管你自己的函数,别人的函数参数是什么样子,鬼知道?还好Python提供了可变参数*args和关键字参数**kwargs,有了这两个参数,装饰器就可以用于任意目标函数了

def debug(func):     def wrapper(*args, **kwargs):  # 指定宇宙无敌参数         print "[DEBUG]: enter {}()".format(func.__name__)         print 'Prepare and say...',         return func(*args, **kwargs)     return wrapper  # 返回  @debug def say(something):     print "hello {}!".format(something)

至此,你已完全掌握初级的装饰器写法。

高级一点的装饰器

带参数的装饰器和类装饰器属于进阶的内容。在理解这些装饰器之前,最好对函数的闭包和装饰器的接口约定有一定了解。(参见http://betacat.online/posts/p...

带参数的装饰器

假设我们前文的装饰器需要完成的功能不仅仅是能在进入某个函数后打出log信息,而且还需指定log的级别,那么装饰器就会是这样的。

def logging(level):     def wrapper(func):         def inner_wrapper(*args, **kwargs):             print "[{level}]: enter function {func}()".format(                 level=level,                 func=func.__name__)             return func(*args, **kwargs)         return inner_wrapper     return wrapper  @logging(level='INFO') def say(something):     print "say {}!".format(something)  # 如果没有使用@语法,等同于 # say = logging(level='INFO')(say)  @logging(level='DEBUG') def do(something):     print "do {}...".format(something)  if __name__ == '__main__':     say('hello')     do("my work")

是不是有一些晕?你可以这么理解,当带参数的装饰器被打在某个函数上时,比如@logging(level='DEBUG'),它其实是一个函数,会马上被执行,只要这个它返回的结果是一个装饰器时,那就没问题。细细再体会一下。

基于类实现的装饰器

装饰器函数其实是这样一个接口约束,它必须接受一个callable对象作为参数,然后返回一个callable对象。在Python中一般callable对象都是函数,但也有例外。只要某个对象重载了__call__()方法,那么这个对象就是callable的。

class Test():     def __call__(self):         print 'call me!'  t = Test() t()  # call me

像__call__这样前后都带下划线的方法在Python中被称为内置方法,有时候也被称为魔法方法。重载这些魔法方法一般会改变对象的内部行为。上面这个例子就让一个类对象拥有了被调用的行为。

回到装饰器上的概念上来,装饰器要求接受一个callable对象,并返回一个callable对象(不太严谨,详见后文)。那么用类来实现也是也可以的。我们可以让类的构造函数__init__()接受一个函数,然后重载__call__()并返回一个函数,也可以达到装饰器函数的效果。

class logging(object):     def __init__(self, func):         self.func = func      def __call__(self, *args, **kwargs):         print "[DEBUG]: enter function {func}()".format(             func=self.func.__name__)         return self.func(*args, **kwargs) @logging def say(something):     print "say {}!".format(something)

带参数的类装饰器

如果需要通过类形式实现带参数的装饰器,那么会比前面的例子稍微复杂一点。那么在构造函数里接受的就不是一个函数,而是传入的参数。通过类把这些参数保存起来。然后在重载__call__方法是就需要接受一个函数并返回一个函数。

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 wrapper  #返回函数  @logging(level='INFO') def say(something):     print "say {}!".format(something)

内置的装饰器

内置的装饰器和普通的装饰器原理是一样的,只不过返回的不是函数,而是类对象,所以更难理解一些。

@property

在了解这个装饰器前,你需要知道在不使用装饰器怎么写一个属性。

def getx(self):     return self._x  def setx(self, value):     self._x = value      def delx(self):    del self._x  # create a property x = property(getx, setx, delx, "I am doc for x property")