Decorator修饰器

Decorator修饰器

我在看mmcv源码的时候,发现库中的mm.Config.fromfile模块中(可能其他模块也有)出现了很多修饰器,因此现在对修饰器的定义以及作用,利用网上的资源进行一个总结。

闭包

先看一下闭包的解释:

在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。

看下面这个例子:

# print_msg是外围函数
def print_msg():
    msg = "I'm closure"

    # printer是嵌套函数
    def printer():
        print(msg)

    return printer

# 这里获得的就是一个闭包
closure = print_msg()
# 输出 I'm closure
closure()

msg是一个局部变量,在print_msg函数执行之后应该就不会存在了,但是inner函数printer引用了这个变量,将这个局部变量封闭在了嵌套函数中,这样就形成了一个闭包。【inner函数和变量msg形成了一个闭包】

闭包其实就是引用了自由变量的函数,这个函数保存了执行的上下文,可以脱离原本的作用域独立存在。

用函数作Decorator

  • 首先来一个demo,看一下decorator的简单用法:

    # 参数fn为一个函数
    def hello(fn):
    	def wrapper():
    		print("hello, {}".format(fn.__name__)) # 函数的__name__属性为函数的名字
    		fn()
    		print("goodbye, {}".format(fn.__name__))
    	return wrapper
    	
    @hello
    def foo():
    	print("I am foo")
    
    foo()
    

    当运行上述代码,会看到如下输出:

    hello, foo
    i am foo
    goodby, foo
    

    由上可以分析一下这个输出现象:

    (1)函数foo前面有一个@hello的“注解”,hello就是我们在上面定义的函数hello;

    (2)在hello函数中,其需要一个fn作为参数(这就是用来做回调的函数);

    (3)hello函数中返回了一个inner函数(就是函数中的函数)wrapper,这个wrapper回调了传进来的fn,并在回调前后加了两条语句。

    这里,“装饰”体现在,hello装饰了foo的每次调用foo().

  • Decorator的本质

    装饰器的使用方法很固定:

    (1)先定义一个装饰函数(帽子)(也可以用类、偏函数实现)。

    (2)再定义业务函数,或者类(人)。

    (3)最后把这顶帽子扣在这个人头上。

    对于Python 的这个@注解语法糖(语法糖就是程序语言中提供[奇技淫巧]的一种手段和方式而已。 通过这类方式编写出来的代码,可读性和易用性都更高,好似糖一般的语法。)来说,当你在用某个@decorator来修饰某个函数func时,如下所示:

    @decorator
    def func():
    	pass
    

    其解释器会解释成下面的语句:

    func = dectoror(func)
    

    这就很清楚了,就是把func函数当作参数传到decorator函数中,然后再回调func函数。但这里需要注意一点,这里还有一个赋值语句,把decorator这个函数的返回值赋值回了原来的func函数。因此上面的那个小demo也可以被翻译为:

    foo = hello(foo)
    

    这是一条语句,并且还被执行了。在这个demo中,hello(foo)返回了wrapper()函数,所以,其实foo变成了wrapper的一个变量,而后面我们执行foo(),其实变成了执行wrapper,这就是decorator的本质(可以看作foo是wrapper的一个“装饰”)。

  • 带参数及多个Decorator

    (1)多个decorator:

    @decorator_one
    @decorator_two
    def func():
    	pass
    

    相当于:

    func = decorator_one(decorator_two(func))
    

    (2)带参数的decorator

    @decorator(arg1, arg2)
    def func():
    	pass
    

    相当于:

    func = decorator(arg1, arg2)(func)
    

    即这里的decorator(arg1, arg2)会返回一个“真正的”decorator。

    例子1:多个decorator

    """
    补充知识:*args和**kwargs
    打包(pack):*args是把传入的多个参数打包成元组,**kwargs是把传入的多个关键字参数打包成字典。 
    拆分(unpack):*args是把打包了的参数拆成单个的,依次赋值给函数的形参,**kwargs是把字典的键值拆成单个的,依次赋值给函数的形参。
    """
    def makeHtmlTag(tag, *args, **kwds):
        def real_decorator(fn):
            css_class = " class='{0}'".format(kwds["css_class"]) if "css_class" in kwds else ""
            def wrapped(*args, **kwds):
                return "<"+tag+css_class+">" + fn(*args, **kwds) + "</"+tag+">"
            return wrapped
        return real_decorator
      
    @makeHtmlTag(tag="b", css_class="bold_css")
    @makeHtmlTag(tag="i", css_class="italic_css")
    def hello():
        return "hello world"
      
    print hello()
      
    # 输出:
    # <b class='bold_css'><i class='italic_css'>hello world</i></b>
    

    在上面这个例子中,我们可以看到:makeHtmlTag有两个参数。所以,为了让 hello = makeHtmlTag(arg1, arg2)(hello) 成功,makeHtmlTag 必需返回一个decorator(这就是为什么我们在makeHtmlTag中加入了real_decorator()的原因),这样一来,我们就可以进入到 decorator 的逻辑中去了—— decorator得返回一个wrapper,wrapper里回调hello。看似那个makeHtmlTag() 写得层层叠叠,但是,已经了解了本质的我们觉得写得很自然。

    例子2:带参数的装饰器

    def Log(deco_name):
        def logger(func):
            def wrapper(*args,**kw):
                print('Info from {}'.format(deco_name))
                print('{} is called...'.format(func.__name__))
                func(*args,**kw)
            return wrapper
        return logger
    
    @Log('logger')
    def add(x,y):
        print(x+y)
    
    if __name__ == '__main__':
        add(3,5)
    
  • 用decorator来装饰类:

    Python中的单例模式,有一种写法就是用装饰器实现的

    def singleton(cls): # cls是类本身,不是实例对象本身
        def get_instance(*args, **kw):
            print('class name:',cls.__name__)
            #判断是不是已经有了这个类的实例,如果有了,就直接返回实例
            if not cls.__name__ in instances:
                instance = cls(*args, **kw) # 实例化在这里被回调
                instances[cls.__name__] = instance
            return instances[cls.__name__]
        return get_instance
    
    @singleton
    class User:
        _instance = None
        def __init__(self, name):
            self.name = name
    
    if __name__ == '__main__':
        usr1=User('wyuheng')
        print('user name:', usr1.name)
        usr2=User('yohuna')
        print('user name:',usr2.name)
    

    输出:

    class name: User
    user name: wyuheng
    class name: User
    user name: wyuheng
    

    由此看出,User类通过singleton函数的装饰,只能被实例一次。并且注意,当User类被实例化这一行为,被singleton函数给装饰了,并由get_instance函数进行回调来实现。相当于singleton函数装饰了User的每次实例化。

用class作decorator

  • 首先还是来看个例子:

    class myDecorator(object):
        def __init__(self, fn):
            print "inside myDecorator.__init__()"
            self.fn = fn
      	# __call__的用法
      	# 该方法的功能类似于在类中重载 () 运算符,使得类实例对象可以像调用普通函数那样,以“对象名()”的形式来调用__call__函数。
        def __call__(self):
            self.fn()
            print "inside myDecorator.__call__()"
      
    @myDecorator
    def aFunction():
        print "inside aFunction()"
      
    print "Finished decorating aFunction()"
      
    aFunction()
      
    # 输出:
    # inside myDecorator.__init__()
    # Finished decorating aFunction()
    # inside aFunction()
    # inside myDecorator.__call__()
    

    上述这个例子,用类的方式声明了一个decorator。我们可以看到这个类中有两个成员【注意下列方法的调用时机】:

    (1)__ init __ (),这个方法是在我们给某个函数进行装饰时被调用,所以需要一个fn的参数,也就是被装饰的函数。

    (2)__ call __(),这个方法是在我们调用被装饰的那个函数时被调用的。

​ 从输出来看,在aFunction函数被调用前,就已经被装饰好了,即已经调用了init()函数,然后一调用aFunction,就会回调myDecorator中的call函数。相当于myDecorator装饰了aFunction的每次调用。

Python自带装饰器

python有三种函数装饰器@staticmethod、@classmethod 和 @property,分别是修饰静态方法,类方法,属性方法(把方法当做属性)

  • @staticmethod

    用该装饰器装饰的类方法,可以不通过实例化对象来调用,而直接通过类本身来调用。【相当于C++中的static成员函数】,如下:

    class C(object):
        @staticmethod
        def f():
            print('runoob');
     
    C.f();          # 静态方法无需实例化即可调用
    

    静态方法不能访问类的成员方法和变量。

  • @classmethod

    classmethod修饰符对应的函数不需要实例化,不需要 self 参数,但第一个参数需要是表示自身类的 cls 参数,可以来调用类的属性,类的方法,实例化对象等。例子如下

    class A(object):
        bar = 1
        def func1(self):  
            print ('foo') 
        @classmethod
        def func2(cls): # cls表示类本身,self表示实例对象本身
            print ('func2')
            print (cls.bar)
            cls().func1()   # 调用 foo 方法
     
    A.func2()               # 不需要实例化
    

    输出:

    func2
    1
    foo
    

    此时,实例化后的对象,如a=A()不能访问声明为类方法【@classmethod装饰的】的方法

  • @property

    我们可以使用@property装饰器来创建只读属性,@property装饰器会将方法转换为相同名称的只读属性,可以与所定义的属性配合使用,这样可以防止属性被修改。

    使用场景

    (1)修饰方法,使方法可以像属性一样访问,函数调用时不用加():

    class DataSet(object):
      @property
      def method_with_property(self): ##含有@property
          return 15
      def method_without_property(self): ##不含@property
          return 15
    
    l = DataSet()
    print(l.method_with_property) # 加了@property后,可以用调用属性的形式来调用方法,后面不需要加()。
    print(l.method_without_property())  #没有加@property , 必须使用正常的调用方法的形式,即在后面加()
    

    注意,被@property修饰之后,就不能在调用方法的时候,加()了。

    (2)与所定义的属性配合使用,这样可以防止属性被修改。【这个重要】

    由于python进行属性的定义时,没办法设置私有属性,因此要通过@property的方法来进行设置。这样可以隐藏属性名,让用户进行使用的时候无法随意修改。

    class DataSet(object):
        def __init__(self):
            self._images = 1
            self._labels = 2 #定义属性的名称
        @property
        def images(self): #方法加入@property后,这个方法相当于一个属性,这个属性可以让用户进行使用,而且用户有没办法随意修改。
            return self._images 
        @property
        def labels(self):
            return self._labels
    l = DataSet()
    #用户进行属性调用的时候,直接调用images即可,而不用知道属性名_images,因此用户无法更改属性,从而保护了类的属性。
    print(l.images) # 加了@property后,可以用调用属性的形式来调用方法,后面不需要加()。
    

    这样之后,用户每次以为调用的是images等属性,但其实调用的是方法,这样就可以隐藏真实的属性,防止被外部改变。【真实属性的命名一般在变量名前加一个下划线_】

后言

Python中装饰器的作用无处不在,后续见到具体的用法再做具体的笔记。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值