zoukankan      html  css  js  c++  java
  • Python 装饰器学习以及实际使用场景实践

    前言

    前几天在看Flask框架,对于非常神奇的@语法,不是非常的理解,回来补装饰器的功课。阅读很多的关于装饰器的文章,自己整理一下,适合自己的思路的方法和例子,与大家分享。

    app = Flask(__name__)
     
    @app.route("/")
    def hello():
        return "Hello World!"
    

    1、装饰器是什么

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

    2、装饰器的语法

    装饰器的语法如下:

    当前Python的装饰器语法如下:
    @dec2
    @dec1
    def func(arg1, arg2, ...):
    	....
    	return funx
    
    上面的代码相当于:
    
    def func(arg1, arg2, ...):
    	pass
    func = dec2(dec1(func))
    

    装饰器可以用def的形式来定义。装饰器接收一个可调用对象作为输入参数,并返回一个新的可调用对象。
    装饰器新建了一个可调用对象,也就是return 返回的函数funx,在新增的函数中,可以添加我们需要的功能,并通过调用原有函数来实现原有函数的功能。

    3、装饰器的使用

    3.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.
    

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

    3.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(1, 2)
    myfunc(3, 4)
    

    输出:
    before myfunc() called.
    myfunc() called.
    After myfunc() called. result: 3

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

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

    3.3装饰器带参数

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

    输出:
    nter decorator demo
    Enter MyFunc

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

    3.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)
    

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

    3.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/rhcad/archive/2011/12/21/2295507.html

    http://www.cnblogs.com/Jifangliang/archive/2008/07/22/1248313.html

    http://www.cnblogs.com/vamei/archive/2013/02/16/2820212.html

  • 相关阅读:
    Spark2.0机器学习系列之12: 线性回归及L1、L2正则化区别与稀疏解
    Spark2.0机器学习系列之11: 聚类(幂迭代聚类, power iteration clustering, PIC)
    mysql 5.7.16多源复制
    Windows版 mysql 5.7.16安装
    一分钟经理人
    转 spring官方文档中文版
    MySQL执行计划 EXPLAIN参数
    sql 转 markdown
    Linux下安装MySQL5.6
    MyBatis完全使用指南
  • 原文地址:https://www.cnblogs.com/StitchSun/p/4600835.html
Copyright © 2011-2022 走看看