【python】Notas de estudio de cierre de Python (cierre), decorador (Decorador) y mecanismo de registro (Registro).


prefacio

Recientemente, entré en contacto con el marco MMCV y descubrí que el marco MMCV introdujo un método de mecanismo de registro (Registro) para facilitar el reemplazo de la columna vertebral, el optimizador, la estrategia de aprendizaje y otros módulos funcionales, que pueden administrar de manera efectiva el contenido en el aprendizaje profundo. estructura. Al mismo tiempo, también es conveniente para los usuarios construir su propia red mediante la colocación flexible de interfaces externas. Para obtener una comprensión profunda del principio del mecanismo de registro, lo he estudiado y organizado, y lo he compartido con lectores y amigos.

Este artículo se divide principalmente en cuatro partes:
la primera parte es una breve introducción a pythonPrincipio de cierre, los cierres son la base de los decoradores.
La segunda parte es una breve introducción a python.Principio del decorador, el mecanismo de registro es el escenario de aplicación del decorador.
La tercera parte es una breve introducción.mecanismo de registro, con ejemplos de código python adjuntos.
La cuarta parte es una breve introducción.Código del mecanismo de registro del marco MMCVy adjunte comentarios de código relevantes.

Debido a la superficialidad de mi estudio, si hay una desviación en mi comprensión de la suya, espero que puedan señalarlo a tiempo. Finalmente, si crees que te es útil, puedes darle un me gusta al hermanito. ⌣ ¨ \ddot\sonrisa¨

Los materiales de referencia son los siguientes:
1. Decorador de funciones de Python | tutorial para principiantes
2. Comprender el concepto de cierre , autor: alpha_panda.
3. Espacio de nombres y alcance de Python3
4. Modelo personalizado de mmsegment (8) , autor: alex1801.
5. Mecanismo de registro del registro , autor: ~HardBoy~.
6. Para comprender a los decoradores de Python, basta con leer este artículo , autor: Liu Zhijun.

1. Conceptos de premisa

Antes de comenzar a aprender, debe comprender 2 conceptos de antemano, como se indica a continuación.
(1) Para Python, todo es un objeto, incluidas las funciones mismas. Por lo tanto, las funciones se pueden asignar a las variables y las llamadas a funciones se pueden realizar a través de las variables. (Las funciones también se pueden asignar a funciones)

def base():
    print("这是基础函数")
def derived():
    print("这是派生函数")
    
# 以变量的形式调用
Var = base
Var()
# 另外,函数也是可以赋值给函数的,以函数的形式调用
derived = base
derived()

inserte la descripción de la imagen aquí
Con base en los conceptos anteriores, se puede concluir que las funciones en realidad se pueden pasar como parámetros a funciones nuevas.

def base():
    print("这是基础函数")
def derived(func):
    func()
    print("这是派生函数")
    
# 以函数的形式调用
derived(base)

inserte la descripción de la imagen aquí
(2) Una función interna se puede definir en una función externa y la función interna se puede devolver.

def outsideFunc():
    print("这是外部函数")
    # 在派生函数内定义基本函数
    def insideFunc():
        print("这是内部函数")
    return insideFunc

Var = outsideFunc()
print(Var)
Var()

inserte la descripción de la imagen aquí

2. Cierre Python (cierre)

La introducción a los cierres en este artículo es relativamente simple. Para obtener más detalles, consulte el blog: Comprender el concepto de cierres , que se explica con gran detalle. También enumera los puntos en los que las funciones de cierre son propensas a errores. Este artículo se centra en funciones de registro, por lo que es sólo una breve introducción.

Concepto: Una función que puede referirse a variables fuera del alcance y también puede usarse dentro de la función se llama función de cierre. Las variables fuera de este ámbito se denominan variables libres. Esta variable libre es diferente de la variable estática modificada por C++, y también diferente de la variable global, que está ligada a la función de cierre.

Tenga en cuenta dos puntos:
1. Entre la función de cierre y la función de cierre, las variables libres no interferirán entre sí.
2. Las variables libres de la función de cierre se pasarán a la siguiente función de cierre.

def outside():
    # 此处obj为自由变量
    obj = []
    # inside就被称为闭包函数
    def inside(name):
        obj.append(name)
        print(obj)
    return inside
    
Lis = outside()
Lis("1")
# 对闭包函数自由变量的修改会传到下一次闭包函数中。
Lis("2")
Lis("3")
print("-"*20)
# 闭包函数和闭包函数之间的自由变量不会相互影响。
Lis2 = outside()
Lis2("4")
Lis2("5")
Lis2("6")

inserte la descripción de la imagen aquí

Como se puede ver en el ejemplo anterior, obj es una variable libre para la función de cierre interior. Cada vez que se llama a la función de cierre interior, se agregará un nuevo valor a obj y se pasará a la siguiente llamada de la función de cierre interior.
Aquí, el papel de la variable libre obj en relación con la función de cierre interna es equivalente a una "variable global", pero el alcance es solo para el entorno externo, incluida la variable libre y la función de cierre. En el ejemplo anterior, se refiere a el ámbito exterior. Python llama a este alcance el alcance fuera de la función de cierre (Incluyendo).

Conocimientos complementarios:
Python tiene cuatro ámbitos:
ámbito local (Local): la capa más interna, que contiene variables locales, como dentro de una función/método.
Ámbito fuera de la función de cierre (Enclosing): contiene variables no locales (no locales) y no globales (no globales). Por ejemplo, dos funciones anidadas, una función (o clase) A contiene una función B, entonces para el nombre en B, el ámbito en A no es local.
Ámbito global (Global): la capa más externa del script actual, como las variables globales del módulo actual.
Ámbito integrado (Integrado): contiene variables/palabras clave integradas, etc., y finalmente se busca.
La secuencia de reglas se muestra en la siguiente figura:
inserte la descripción de la imagen aquí

Como se muestra en la figura anterior, si python no puede encontrar la variable de función correspondiente en el ámbito local, la buscará localmente fuera del ámbito local (como los cierres), y si no puede encontrarla, buscará para ello en el ámbito global, y luego vaya a la función integrada.dominio. Para obtener contenido más detallado aquí, consulte el espacio de nombres y alcance de Python3 del blog .

3. decorador pitón (Decorador)

Después de comprender el principio de cierre, veamos la esencia de los decoradores. Un decorador es en realidad una función avanzada que encapsula una función a través del principio de cierre y devuelve la función. El propósito de la encapsulación es expandir funciones adicionales sobre la base de mantener las funciones originales. Para obtener más información, consulte los siguientes ejemplos.

def add(arr1, arr2):
    return arr1 + arr2
def decorator(func):
    def wrapper(arr1, arr2):
        print("实现新的功能,例如数据加1的功能")
        arr1 += 1
        arr2 += 1
        return func(arr1, arr2)
    return wrapper

print(add(2, 3))

add = decorator(add)
print(add(2, 3))

inserte la descripción de la imagen aquí

Como se puede ver en el código, esperamos agregar una nueva función para aumentar el parámetro en uno sobre la base de mantener la función de agregar datos. Como de costumbre, necesitamos agregar nuevo contenido a la función anterior, lo que destruirá la estructura original. Por lo tanto, sobre la base de conservar la estructura de función original, se pueden agregar nuevas funciones adicionales a través de decoradores. El decorador anterior es una función de decorador, y el siguiente add = decorator(add) es su método de llamada, pero en python, puede usar la sintaxis @ para simplificar la declaración de llamada. Como sigue:

def decorator(func):
    def wrapper(arr1, arr2):
        print("实现新的功能,例如数据加1的功能")
        arr1 += 1
        arr2 += 1
        return func(arr1, arr2)
    return wrapper

@decorator
# @decorator就等价于add = decorator(add)
def add(arr1, arr2):
    return arr1 + arr2

print(add(2, 3))

Puedes ver que @decorator es equivalente a add = decorator(add), "@" pasará la función modificada como parámetro a la función decorador

Añade un pequeño punto:
Después de pasar por la función de decorador, el contenido de la función de agregar se ha convertido en realidad en la función de envoltorio, por lo que su otro contenido de descripción relacionado también se ha convertido en el contenido de descripción del envoltorio, como __name__. Por lo tanto, si no desea modificar el contenido de la descripción original, puede usar la función de decoración @wraps(func) en el paquete de funciones functools para lograrlo. Esta función puede copiar el contenido de la descripción de la función original.

# 不加@wraps的情况下
def decorator(func):
    def wrapper(arr1, arr2):
        print("实现新的功能,例如数据加1的功能")
        arr1 += 1
        arr2 += 1
        return func(arr1, arr2)
    return wrapper
def add(arr1, arr2):
    return arr1 + arr2
#原本add函数
print(add.__name__)
#通过装饰器修饰后
add = decorator(add)
print(add.__name__)

inserte la descripción de la imagen aquí

from functools import wraps
# 添加@wraps的情况下
def decorator(func):
    @wraps(func)
    #@wraps(func) 等同于 wrapper = wraps(func)(wrapper)
    def wrapper(arr1, arr2):
        print("实现新的功能,例如数据加1的功能")
        arr1 += 1
        arr2 += 1
        return func(arr1, arr2)
    return wrapper

def add(arr1, arr2):
    return arr1 + arr2
    
#原本add函数
print(add.__name__)
#通过装饰器修饰后
add = decorator(add)
print(add.__name__)

inserte la descripción de la imagen aquí

4. Mecanismo de registro (Registro)

Concepto: El mecanismo de registro es principalmente para realizar el mapeo de la cadena ingresada por el usuario a la función o clase requerida, lo cual es conveniente para la gestión de proyectos y el uso del usuario. El mecanismo de registro puede construir una relación de mapeo a través de decoradores de python. Por ejemplo: MMCV también se realiza mediante el método decorador.

Hay tres pasos principales para completar el mecanismo de registro:
(1) Escriba la clase del mecanismo de registro.
(2) Instanciar un objeto del mecanismo de registro, es decir, construir el registro.
(3) Agregar contenido hacia y desde el registro a través del principio del decorador, es decir, para lograr el registro de contenido

4.1 Escriba la clase del mecanismo de registro.

class Registry:
    def __init__(self, name=None):
        # 生成注册列表的名字, 如果没有给出,则默认是Registry。
        if name == None:
            self._name = "Registry"
        self._name = name
        #创建注册表,以字典的形式。
        self._obj_list = {
    
    }

    def __registry(self, obj):
        """
        内部注册函数
        :param obj:函数或者类的地址。
        :return:
        """
        #判断是否目标函数或者类已经注册,如果已经注册过则标错,如果没有则进行注册。
        assert(obj.__name__ not in self._obj_list.keys()), "{} already exists in {}".format(obj.__name__, self._name)
        self._obj_list[obj.__name__] = obj

    def registry(self, obj=None):
        """
        # 外部注册函数。注册方法分为两种。
        # 1.通过装饰器调用
        # 2.通过函数的方式进行调用

        :param obj: 函数或者类的本身
        :return:
        """
        # 1.通过装饰器调用
        if obj == None:
            def _no_obj_registry(func__or__class, *args, **kwargs):
                self.__registry(func__or__class)
                # 此时被装饰的函数会被修改为该函数的返回值。
                return func__or__class
                                                
            return _no_obj_registry
        #2.通过函数的方式进行调用
        self.__registry(obj)

    def get(self, name):
        """
        通过字符串name获取对应的函数或者类。
        :param name: 函数或者类的名称
        :return: 对应的函数或者类
        """
        assert (name in self._obj_list.keys()), "{}  没有注册".format(name)
        return self._obj_list[name]

Esta clase de mecanismo de registro incluye principalmente tres funciones miembro, a saber, __registro, registro, obtener y dos variables miembro self._name y self._obj_list.

Variables miembro:
1.variable self._name: Indica el nombre de este registro, si no se proporciona, por defecto es Registro.
2. Variable self._obj_list: representa el registro en forma de diccionario, es decir, la relación de mapeo entre cadenas de caracteres y nombres de funciones correspondientes.
Funciones miembro:
1. Función de registro: registra la función entrante de dos maneras, una es modificar la variable libre self._obj_list a través de la función de cierre _no_obj_registry. La otra es completar el registro directamente pasando el parámetro obj de la función de registro. La implementación específica de estos dos métodos es realizar el registro a través de la función __registry
2. La función __registry: registra los parámetros de la función pasados ​​e informa un error si existe, y completa el registro si no existe.
3. Obtener función: realice la asignación del nombre de la cadena a la función o nombre de clase correspondiente buscando en el registro y devuelva la función o clase con el nombre correspondiente.

4.2 Crear un registro

Según la clase del mecanismo de registro, se crea una instancia de un objeto, y este objeto es el registro que necesitamos.

# 生成注册表
REGISTRY_LIST = Registry("REGISTRY_LIST")

4.3 Registro de contenido

Agregar contenido desde y hacia el registro a través del principio del decorador, es decir, para lograr el registro de contenido. En el siguiente ejemplo, registre la función create_by_decorator a través de la sentencia @REGISTRY_LIST.registry().

@REGISTRY_LIST.registry()等价于
test_by_decorator = REGISTRY_LIST.registry()(test_by_decorator),
即_no_obj_registry(test_by_decorator)

# 通过装饰器调用
@REGISTRY_LIST.registry()
# @REGISTRY_LIST.registry()等价于test_by_decorator = REGISTRY_LIST.registry()(test_by_decorator),即_no_obj_registry(test_by_decorator)
def create_by_decorator():
    print("通过装饰器完成注册的函数")


def create_by_function():
    print("直接通过registry函数进行注册")
#当然也可以直接通过传入registry函数进行注册。
REGISTRY_LIST.registry(create_by_function)

#通过字符串来获取对应函数名称的函数
test1 = REGISTRY_LIST.get("create_by_decorator")
test1()
test2 = REGISTRY_LIST.get("create_by_function")
test2()

inserte la descripción de la imagen aquí

5. Mecanismo de registro de MMCV

Porque ejecuto el marco mmcv en Windows. Por lo tanto, la ruta del archivo de mi archivo registration.py es F:\SegFormer-master\mmcv-1.2.7\mmcv\utils\registry.py, los lectores pueden encontrar la ubicación del archivo registty.py de mmcv en sus propios proyectos según su propia situación, Linux debe instalarse bajo el paquete mmcv. El código general es el siguiente. Lo desmontaremos más tarde para echar un vistazo más de cerca.

import inspect
import warnings
from functools import partial

from .misc import is_seq_of

class Registry:
    """A registry to map strings to classes.

    Args:
        name (str): Registry name.
    """

    def __init__(self, name):
        self._name = name
        self._module_dict = dict()

    def __len__(self):
        return len(self._module_dict)

    def __contains__(self, key):
        return self.get(key) is not None

    def __repr__(self):
        format_str = self.__class__.__name__ + \
                     f'(name={
      
      self._name}, ' \
                     f'items={
      
      self._module_dict})'
        return format_str

    @property
    def name(self):
        return self._name

    @property
    def module_dict(self):
        return self._module_dict

    def get(self, key):
        """Get the registry record.

        Args:
            key (str): The class name in string format.

        Returns:
            class: The corresponding class.
        """
        return self._module_dict.get(key, None)

    def _register_module(self, module_class, module_name=None, force=False):
        if not inspect.isclass(module_class):
            raise TypeError('module must be a class, '
                            f'but got {
      
      type(module_class)}')

        if module_name is None:
            module_name = module_class.__name__
        if isinstance(module_name, str):
            module_name = [module_name]
        else:
            assert is_seq_of(
                module_name,
                str), ('module_name should be either of None, an '
                       f'instance of str or list, but got {
      
      type(module_name)}')
        for name in module_name:
            if not force and name in self._module_dict:
                raise KeyError(f'{
      
      name} is already registered '
                               f'in {
      
      self.name}')
            self._module_dict[name] = module_class

    def deprecated_register_module(self, cls=None, force=False):
        warnings.warn(
            'The old API of register_module(module, force=False) '
            'is deprecated and will be removed, please use the new API '
            'register_module(name=None, force=False, module=None) instead.')
        if cls is None:
            return partial(self.deprecated_register_module, force=force)
        self._register_module(cls, force=force)
        return cls

    def register_module(self, name=None, force=False, module=None):
        """Register a module.

        A record will be added to `self._module_dict`, whose key is the class
        name or the specified name, and value is the class itself.
        It can be used as a decorator or a normal function.

        Example:
            >>> backbones = Registry('backbone')
            >>> @backbones.register_module()
            >>> class ResNet:
            >>>     pass

            >>> backbones = Registry('backbone')
            >>> @backbones.register_module(name='mnet')
            >>> class MobileNet:
            >>>     pass

            >>> backbones = Registry('backbone')
            >>> class ResNet:
            >>>     pass
            >>> backbones.register_module(ResNet)

        Args:
            name (str | None): The module name to be registered. If not
                specified, the class name will be used.
            force (bool, optional): Whether to override an existing class with
                the same name. Default: False.
            module (type): Module class to be registered.
        """
        if not isinstance(force, bool):
            raise TypeError(f'force must be a boolean, but got {
      
      type(force)}')
        # NOTE: This is a walkaround to be compatible with the old api,
        # while it may introduce unexpected bugs.
        if isinstance(name, type):
            return self.deprecated_register_module(name, force=force)

        # use it as a normal method: x.register_module(module=SomeClass)
        if module is not None:
            self._register_module(
                module_class=module, module_name=name, force=force)
            return module

        # raise the error ahead of time
        if not (name is None or isinstance(name, str)):
            raise TypeError(f'name must be a str, but got {
      
      type(name)}')

        # use it as a decorator: @x.register_module()
        def _register(cls):
            self._register_module(
                module_class=cls, module_name=name, force=force)
            return cls

        return _register

Específicamente, se puede ver que la clase Registry de mmcv contiene dos variables miembro self._name y self._module_dict, y las seis funciones principales, name function, module_dict function, get function, _register_module function, deprecated_register_module function y register_module function. Presentamos brevemente cada uno.

Variables miembro:
1.variable self._name: Indica el nombre de este registro.
2. Variable self._module_dict: representa el registro en forma de diccionario, es decir, la relación de mapeo entre cadenas y nombres de funciones correspondientes.
Funciones miembro:
1. La función name y la función module_dict están todas decoradas con el decorador @property. El decorador @property incorporado de Python es responsable de convertir un método en una llamada de propiedad.
2. función register_module: complete el registro de la clase de destino, el código específico es el siguiente y se ha comentado el significado de la función.

def register_module(self, name=None, force=False, module=None):
        """注册一个模型

        类名称将被添加到变量self._module_dict中, 该变量的键值是类别名或者专属名字。
        它可以通过装饰器或者函数直接调用。

        Example:
            >>> backbones = Registry('backbone')
            >>> @backbones.register_module()
            >>> class ResNet:
            >>>     pass

            >>> backbones = Registry('backbone')
            >>> @backbones.register_module(name='mnet')
            >>> class MobileNet:
            >>>     pass

            >>> backbones = Registry('backbone')
            >>> class ResNet:
            >>>     pass
            >>> backbones.register_module(ResNet)

        Args:
            name (str | None): 要注册的模块名称。如果未指定,则将使用类名。
            force (bool, optional): 是否用相同的名称重写现有的类。默认值:False。
            module (type): 要注册的模块类。
        """
        #---------------------------------------------------------------------
        #判断输入force参数是否正确。
        #---------------------------------------------------------------------
        if not isinstance(force, bool):
            raise TypeError(f'force must be a boolean, but got {
      
      type(force)}')
        #---------------------------------------------------------------------
        #注意:这是一个与旧api兼容的演练,而它可能会引入意想不到的错误。
        #---------------------------------------------------------------------
        if isinstance(name, type):
            return self.deprecated_register_module(name, force=force)
        #---------------------------------------------------------------------
        #判断module是否存在,如果存在则直接进行注册。并返回module
        #---------------------------------------------------------------------
        # use it as a normal method: x.register_module(module=SomeClass)
        if module is not None:
            self._register_module(
                module_class=module, module_name=name, force=force)
            return module
        #---------------------------------------------------------------------
        #判断输入的name参数是否正确
        #---------------------------------------------------------------------
        # raise the error ahead of time
        if not (name is None or isinstance(name, str)):
            raise TypeError(f'name must be a str, but got {
      
      type(name)}')
        #---------------------------------------------------------------------
        #如果module不存在,则通过装饰器的方式进行注册。
        #---------------------------------------------------------------------
        # use it as a decorator: @x.register_module()
        def _register(cls):
            self._register_module(
                module_class=cls, module_name=name, force=force)
            return cls

        return _register
  1. Función _register_module: implementa concretamente el método de registro de una función o clase. El código específico es el siguiente, la función ha sido comentada.
  def _register_module(self, module_class, module_name=None, force=False):
    """
    具体实现注册方法。
    :param module_class:需要注册的函数本身
    :param module_name:需要注册的函数名称。默认为None
    :param force:是否重写已经存在的函数,默认为False
    """
        #---------------------------------------------------------------------
        #判断module是否是class类。不是类则报错
        #---------------------------------------------------------------------
        if not inspect.isclass(module_class):
            raise TypeError('module must be a class, '
                            f'but got {
      
      type(module_class)}')
        #---------------------------------------------------------------------
        #判断module_name是否存在,不存在则默认函数本身名称
        #---------------------------------------------------------------------
        if module_name is None:
            module_name = module_class.__name__
        #---------------------------------------------------------------------
        #判断module_name是否是个字符串,或者列表
        #---------------------------------------------------------------------
        if isinstance(module_name, str):
            module_name = [module_name]
        else:
            assert is_seq_of(
                module_name,
                str), ('module_name should be either of None, an '
                       f'instance of str or list, but got {
      
      type(module_name)}')
        #---------------------------------------------------------------------
        #针对列表中的字符串进行注册。
        #---------------------------------------------------------------------
        for name in module_name:
            if not force and name in self._module_dict:
                raise KeyError(f'{
      
      name} is already registered '
                               f'in {
      
      self.name}')
            #完成注册
            self._module_dict[name] = module_class

4. Obtener la función: realice la asignación del nombre de la cadena al nombre de la función correspondiente buscando en el registro y devuelva la función con el nombre correspondiente.

    def get(self, key):
        """或者注册表的键值

        Args:
            key (str): 键值必须是字符串

        Returns:
            class: 键值对应的类.
        """
		return self._module_dict.get(key, None)

5. función deprecated_register_module: módulo de registro en desuso. no entiendo esto

    def deprecated_register_module(self, cls=None, force=False):
        
        warnings.warn(
            'The old API of register_module(module, force=False) '
            'is deprecated and will be removed, please use the new API '
            'register_module(name=None, force=False, module=None) instead.')
        if cls is None:
            return partial(self.deprecated_register_module, force=force)
        self._register_module(cls, force=force)
        return cls

Simplemente regístrese a través del Registro de mmcv. La implementación específica es la siguiente. Por supuesto, no es así como MMCV agrega un módulo de registro. Para obtener detalles sobre cómo agregar módulos, puede consultar el modelo personalizado de segmento mm del blog csdn (ocho archivos .

if __name__ == "__main__":

    from torch.optim.adam import Adam
    registry_list = Registry("OPTIM")
    registry_list.register_module(name="registry_adam", module=Adam)
    optim = registry_list.get("registry_adam")
    print(optim)
    print(registry_list.module_dict)

inserte la descripción de la imagen aquí

Resumir

Este artículo resume los principios de cierres, decoradores y mecanismos de registro de python, y enumera el código y los resultados de salida. Sin embargo, este artículo solo toca la parte más superficial. Al estudiar varios blogs de referencia, descubrí que los cierres y los decoradores son mucho más que eso. Si te interesan estos aspectos, puedes entrar en el blog de referencia para seguir aprendiendo. Finalmente, gracias por leer.

Supongo que te gusta

Origin blog.csdn.net/weixin_43610114/article/details/126182474
Recomendado
Clasificación