【Faster RCNN系列】@layer 装饰器

我看的Faster RCNN代码复现代码为:https://github.com/smallcorgi/Faster-RCNN_TF

关于./lib/networks/network.py 中有这么一段代码:

def layer(op):
    """Decorator for composable network layers."""

    def layer_decorated(self, *args, **kwargs):
        # Automatically set a name if not provided.
        name = kwargs.setdefault('name', self.get_unique_name(op.__name__))
        # Figure out the layer inputs.
        if len(self.inputs) == 0:
            raise RuntimeError('No input variables found for layer %s.' % name)
        elif len(self.inputs) == 1:
            layer_input = self.inputs[0]
        else:
            layer_input = list(self.inputs)
        # Perform the operation and get the output.
        layer_output = op(self, layer_input, *args, **kwargs)

        # Add to layer LUT.
        self.layers[name] = layer_output  # 添加到layers这个字典中
        # This output is now the input for the next layer.
        self.feed(layer_output)
        # Return self for chained calls.
        return self  # 返回可以进行:a.func2().func3()
    return layer_decorated

你会发现这个文件夹下面的每个函数都带有一个@layer,刚开始我也不知道这是什么,他的工作原理是什么,在本代码中的是怎么用的。

0.函数也是对象,而且函数对象可以被赋值给变量,所以,通过变量也能调用该函数。

例如

def now():
print('2015-3-12')

if __name__ == '__main__':
f = now # 这里now是一个对象,赋给变量f,f也可以调用函数了。
f()
print(now.__class__) #<class 'function'>

1.什么是装饰器?

在不改变函数本身的定义下增强该函数的功能,即在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。本质上,decorator就是一个返回函数的高阶函数。

def log(func):  # 因为log是一个装饰器,所以它可以接受一个函数作为参数
    def wrapper(*args, **kw):
        print('call %s():' % func.__name__)
        return func(*args, **kw)
    return wrapper

@log
def now():
    print('2015-3-12')

if __name__ == '__main__':
    f=now # 相当于f=now = log(now)
    f()  # 这个相当于执行了f()=now()=log(now())

由于log()是一个decorator,返回一个函数,所以,原来的now()函数仍然存在,只是现在同名的now变量指向了新的函数,于是调用now()将执行新函数,即在log()函数中返回的wrapper()函数。wrapper()函数的参数定义是(*args, **kw),因此,wrapper()函数可以接受任意参数的调用。【注1】。如果需要再返回别的自定义Log文本,则需要三层嵌套的decorator:https://www.liaoxuefeng.com/wiki/1016959663602400/1017451662295584

以上都是可以在廖雪峰老师的网站上看到的,我也是转载自他的网站。但是我依然没有能深入理解decorator,让我们先重新阅读以下layer装饰器函数代码:

def layer(op):  # op相当于要输入的func
    def layer_decorated(self, *args, **kwargs):  # 在装饰器中加入self 即可装饰类中的方法
        # Automatically set a name if not provided.
        name = kwargs.setdefault('name', self.get_unique_name(op.__name__)) #op.__name__的是各个操作函数名,如conv、max_pool
        # Figure out the layer inputs.确认layer的输入,如果是0返回错误,如果是1,则就是inputs[0],大于1,则转化为list
        if len(self.inputs) == 0:
            raise RuntimeError('No input variables found for layer %s.' % name)
        elif len(self.inputs) == 1:
            layer_input = self.inputs[0]
        else:
            layer_input = list(self.inputs)
        # Perform the operation and get the output.
        layer_output = op(self, layer_input, *args, **kwargs)  # 输出即layer_output=方法(self, layer_input, *args, **kwargs)
        # Add to layer LUT.
        self.layers[name] = layer_output # 将输出添加到layers[name]这个字典中,因为有 self.layers = dict(inputs)
        # This output is now the input for the next layer.
        self.feed(layer_output)  #最后执行feed方法,即将上一层的输出作为下一层的输入。,
        # Return self for chained calls.
        return self   # 返回类,就是占个右值,可以用于chained call
    return layer_decorated

3.深入理解装饰器和链式调用

很显然,看了上面的内容基本理解了这个装饰器的作用是什么了,但是把他装饰给了函数,到底是一个什么样的工作流程呢?为什么要return self 要返回类?下面是我自己写的实验代码。我的装饰器的功能是将类属性age+3。并打印age。fun1是没有加装饰器的函数,fun2是加了装饰器但没有对age属性做修改, fun3是加了装饰器并对age+1。

def layer(func):
    def layer_decorator(self, *arg, **kw):
        self.func1(self.age)
        # func1(self, self.age) # 这个python不知道你说的是谁
        # self.func(self.age) # 这个的func是上面layer装饰的函数而不是类的函数:AttributeError: 'agecnt' object has no attribute 'func'
        self.age = self.age + 3
        age = self.age
        output_layer = func(self, age)
        if isinstance(output_layer,tf.Tensor):
            print(type(output_layer))
        return self
    return layer_decorator


class agecnt():
    #  __init__ should return None,so it shouldn't have decorator return self
    def __init__(self, age):
        self.name = self.__class__
        self.age = age
        print(str(sys._getframe().f_lineno),"line:execute __init__ funcof {}".format(self.name))

    def func1(self, num):
        print('func1(without decorator) age:', num)

    @layer
    def func2(self,age):
        print('func2 age: {}'.format(age))
        return tf.constant('hello')


    @layer
    def func3(self, age):
        age = age + 1
        print('func3 age: {}'.format(age))
        return 123

if __name__ == '__main__':
    a = agecnt(1)
    a = a.func2().func3()  # 这个就是chain calls ,如果上面没有return self,则无法这么写~

    # a.func3()

根据我自己的实验发现:

  1.__init__构造函数不能使用装饰器,其实构造函数 好像也不需要使用构造函数。

  2.首先实例化一个类,然后同时调用func2和func3。但是其实不是调用的func2和func3,而是调用了装饰器函数,而装饰器函数中参数分别是func2和func3。

  3.这个func2,func3如果返回的是return self,则可以进行chain calls,如果没有返回,则不能写成 “a.func2().func3()”这种形式,只能写成:“ a=a.func2();  a=a.func3()  ”的形式。

以上就是我关于装饰器以及链式调用(chain return)的个人理解。

注1:*args 用来将参数打包成tuple给函数体调用;而**kwargs 打包关键字参数成dict给函数体调用

注意点:参数arg、*args、**kwargs三个参数的位置必须是一定的。必须是(arg,*args,**kwargs)这个顺序,否则程序会报错。

def function(arg,*args,**kwargs):
print(arg,args,kwargs)


function(6,7,8,9,a=1, b=2, c=3  # 6 (7,8,9) {'c':3,'a':1,'b':2}

Python中*args和**kwargs的区别:

https://blog.csdn.net/u011596455/article/details/82702301
https://www.cnblogs.com/cwind/p/8996000.html

注2:设计模式 装饰模式:将东西在内部组装完毕,然后在现实出来,但是又不同于建造者模式,建造者模式非常稳定
装饰模式是需要我们把所需的功能按照正确的顺序串联起来进行控制。
装饰模式(decorator):动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生产子类更加灵活。

https://blog.csdn.net/pihailailou/article/details/82813374
https://www.cnblogs.com/Josie-chen/p/8707322.html
https://blog.csdn.net/qq_33297776/article/details/81591592

参考博客:

https://www.liaoxuefeng.com/wiki/1016959663602400/1017451662295584

原文地址:https://www.cnblogs.com/SsoZhNO-1/p/11248621.html