浅谈Python装饰器

一、概念

  装饰器是Python语言中的高级语法。主要的功能是对一个函数、方法、或者类进行加工,作用是为已经存在的对象添加额外的功能,提升代码的可读性。装饰器是设计模式的一种,被用于有切面需求的场景,较为经典的有插入日志、性能测试、事务处理等。在实际应用中也经常会用到装饰器。这篇文章来简单谈一下装饰器的实现方式。

 

二、闭包

  因为装饰器是基于闭包来实现的,这里简单说下Python的闭包。看下面的代码:

def outer():
    var = 3

    def inner():
        print(var)

    return inner

func = outer()
# 注意这个是func是outer返回的,所以它是inner函数 func() # 3 执行inner函数 print(func) # <function outer.<locals>.inner at 0x1022059d8>

我们知道一个函数可以调用其父级作用域的变量,所以函数inner可以打印变量var,函数也可以接收函数和返回函数,这样的函数称作高阶函数,在上面的代码中outer函数就是一个高阶函数。定义好函数之后,首先我们执行outer,并将返回值赋一个变量,因为我们的outer函数返回的也是一个函数,所以这个变量是可以执行的。

三、装饰器的实现

import time


def outer(f):

    def inner(x):
        start_time = time.time()
        f(x)
        end_time = time.time()
        amount_time = end_time - start_time
        print(amount_time)

    return inner


def foo(x):
    print(x)
    time.sleep(2)

foo = outer(foo)

foo("a")

上面的这段代码是否似曾相识?对,就是我们写的闭包。这段代码中,我们定义了一个普通的函数foo,可以理解为我们的业务函数,又定义了一个高阶函数outer,这个高阶函数可以理解为我们给业务函数添加的其他功能。这里是简单的业务时间统计。首先,我们在foo函数中需要将用户传的值进行操作,在实现之后,增加了一个统计时间的需求,这个时候我们的代码已经是完善的代码了,会尽量的避免源代码的修改的。这个时候使用装饰器来完成这个要求是很好的选择。我们先定义一个高阶函数outer,把我们的业务函数当做变量传递给outer,然后给outer的返回值赋一个变量,这个变量名要和我们的业务函数名字一样:foo,这样做是为了尽量少的变更源代码,这样我们在执行foo的时候就会变成执行outer中的inner函数,在inner函数中又执行了我们的业务函数。虽然实现了我们的要求,但是这样看起来代码会很乱,别着急,Python也为我们提供了一种简单的写法:

import time


def outer(f):

    def inner(x):
        start_time = time.time()
        f(x)
        end_time = time.time()
        amount_time = end_time - start_time
        print(amount_time)

    return inner


@outer
def foo(x):
    print(x)
    time.sleep(2)

foo("a")

就是通过一个“@”符号加上我们的装饰器函数名就实现了,这样看起来是不是会好点呢?上面的代码只是为了演示装饰器的实现。装饰器还有很多种使用方法。

 

四、装饰器的使用

4.1 不带参数的装饰器

不带参数的装饰器非常简单:

def deco(func):
    """无参数调用decorator声明时必须有一个参数,这个参数将接收要装饰的方法"""
    print "before myfunc() called."
    func()
    print "after myfunc() called."
    return func

@deco
def myfunc():
    print " myfunc() called."


myfunc()
myfunc()

定义好装饰器后,就可以通过@语法来使用了在函数的定义前调用@+装饰器函数名,即可使用。上面这个装饰器在使用的时候有一个问题,即只在第一次被调用,并且原来的函数多执行一次。执行输出如下:

before myfunc() called.  
 myfunc() called.
  after myfunc() called.
 myfunc() called.   --函数多执行一次的输出
 myfunc() called.   --第二次调用,装饰器不生效

要保证新函数每次被调用,使用下面的方法来定义装饰器

def deco(func):
    """无参数调用decorator声明时必须有一个参数,这个参数将接收要装饰的方法"""
    def _deco():
        print "before myfunc() called."
        func()
        print "after myfunc() called."
        #return func 不需要返回func
    retrun _deco
@deco
def myfunc():
    print " myfunc() called."
    return 'OK'

myfunc()
myfunc()

函数输出如下:

before myfunc() called.
 myfunc() called.
  after myfunc() called.
before myfunc() called.
 myfunc() called.
  after myfunc() called.

这样可以看到,装饰器每次都得到了调用。

4.2带参数的函数进行装饰器

def deco(func):
    def _deco(a, b):
        print("before myfunc() called.")
        ret = func(a, b)
        print("  after myfunc() called. result: %s" % ret)
        return ret
    return _deco

@deco
def myfunc(a, b):
    print(" myfunc(%s,%s) called." % (a, b))
    return a + b
 
myfunc(3, 4)

输出:

before myfunc() called.
myfunc() called.
After myfunc() called. result: 7

内嵌函数的形参和返回值与原函数相同,装饰函数返回内嵌包装函数。

4.3装饰器带参数

def decoWithArgs(arg):
"""由于有参数的decorator函数在调用时只会使用应用时的参数而不接收被装饰的函数做为参数,
   所以必须返回一个decorator函数, 由它对被装饰的函数进行封装处理"""
  def newDeco(func):    #定义一个新的decorator函数
      def replaceFunc():    #在decorator函数里面再定义一个内嵌函数,由它封装具体的操作
          print "Enter decorator %s" %arg    #进行额外操作
          func()    #对被装饰函数进行调用
      return replaceFunc
  return newDeco    #返回一个新的decorator函数

@decoWithArgs("demo")
def MyFunc():    #应用@decoWithArgs修饰的方法
    print "Enter MyFunc"

MyFunc()    #调用被装饰的函数

输出:
nter decorator demo
Enter MyFunc

这个情形适用于原来的函数没有参数,新增加打印的情况。常见适用的地方是增加函数的打印日志。

4.4对参数数量不确定的函数进行装饰

下面的例子是一个邮件异步发送的例子,函数的参数数据部确定,装饰器实现了对于邮件发送函数的异步发送。

from threading import Thread

def async(f):
    def wrapper(*args, **kwargs):
        thr = Thread(target = f, args = args, kwargs = kwargs)
        thr.start()
    return wrapper

@async
def send_async_email(msg):
    mail.send(msg)

def send_email(subject, sender, recipients, text_body, html_body):
    msg = Message(subject, sender = sender, recipients = recipients)
    msg.body = text_body
    msg.html = html_body
    send_async_email(msg)

并且这个装饰器可以适用一切需要异步处理的功能,做到非常好的代码复用。

4.5让装饰器带类参数

class locker:
    def __init__(self):
        print("locker.__init__() should be not called.")
         
    @staticmethod
    def acquire():
        print("locker.acquire() called.(这是静态方法)")
         
    @staticmethod
    def release():
        print("  locker.release() called.(不需要对象实例)")
 
def deco(cls):
    '''cls 必须实现acquire和release静态方法'''
    def _deco(func):
        def __deco():
            print("before %s called [%s]." % (func.__name__, cls))
            cls.acquire()
            try:
                return func()
            finally:
                cls.release()
        return __deco
    return _deco
 
@deco(locker)
def myfunc():
    print(" myfunc() called.")
 
myfunc()
myfunc()

输出为:

before myfunc called [__main__.locker].
locker.acquire() called.(this is staticmethon)
myfunc() called.
  locker.release() called.(do't need object )

before myfunc called [__main__.locker].
locker.acquire() called.(this is staticmethon)
myfunc() called.
  locker.release() called.(do't need object )

装饰器总结

当我们对某个方法应用了装饰方法后, 其实就改变了被装饰函数名称所引用的函数代码块入口点,使其重新指向了由装饰方法所返回的函数入口点。由此我们可以用decorator改变某个原有函数的功能,添加各种操作,或者完全改变原有实现。

 

 

本文中装饰器使用场景及总结转自:http://www.cnblogs.com/StitchSun/p/4600835.html

posted @ 2017-03-12 19:02  善行者无疆  阅读(272)  评论(0编辑  收藏  举报