es.davy.ai

Preguntas y respuestas de programación confiables

¿Tienes una pregunta?

Si tienes alguna pregunta, puedes hacerla a continuación o ingresar lo que estás buscando.

Cómo utilizar el decorador `@dataclass` dentro de otro decorador?

Me gustaría crear un decorador de clase para predefinir algunos atributos y métodos a una clase decorada. Al mismo tiempo, me gustaría que esta clase decorada esté decorada con el decorador @dataclass.
Para hacer las cosas aún más fáciles para el usuario, me gustaría que el usuario sólo tuviera que usar mi decorador para que su clase esté decorada con @dataclass.

Así es como se me ocurrió decorar una clase para que la clase decorada tenga un nuevo atributo id_. Estoy enviando este código para saber si la anidación de decoradores es ‘aceptable’?
Básicamente, no estoy seguro de llamar a un decorador como si fuera una función (aunque es una función), así que me pregunto si podría haber efectos secundarios.

from dataclasses import dataclass

<h1>Mi biblioteca: la complejidad está permitida.</h1>

def indexer(orig_class):
    orig_class = dataclass(orig_class)
    # Copia del __init__ original para llamarlo sin recursión.
    orig_init = orig_class.__init__
    id_ = 10

def __init__(self,*args, **kws):
    self.id_ = id_
    orig_init(self, *args, **kws) # Llama al __init__ original.

orig_class.__init__ = __init__ # Establece el __init__ de la clase a la nuevo

return orig_class

<h1>Entorno del usuario: importando el decorador @indexer: se apunta a la facilidad de uso.</h1>

<h1>Comportamiento esperado es que @indexer proporcione las mismas funciones que @dataclass.</h1>

@indexer
class Truc:
    mu : int

<h1>Prueba</h1>

test = Truc(3)

test.mu
Out[37]: 3

test.id_
Out[38]: 10

Como nota al margen, ¿no sé si el decorador es mejor que la herencia de clases aquí, cuando se trata de añadir atributos y métodos?
¡Gracias por los consejos!
Mejores,
Edición

Para aquellos que puedan leer hasta aquí, y se pregunten sobre el uso entre el decorador y la herencia.
Yendo más lejos en la implementación, finalmente me quedo con el decorador ya que me parece que proporciona una forma más flexible de ajustar lo que habría sido una clase secundaria si hubiera mantenido la herencia.

Con la herencia, no veo cómo la clase padre tiene algún control sobre los atributos secundarios (para hacerlos congelados, por ejemplo) mientras que deja completa libertad para nombrar/definir estos atributos.

Pero esta es realmente una propiedad importante que estoy buscando con las dataclass.

Así que, el estado actual es revisable con el siguiente decorador @indexer.
Deja libertad al usuario para crear una 'data class' con el número de parámetros que desee, nombrándolos como desee.
Pero se hace posible:
- comparar y comprobar la igualdad entre 2 instancias de la clase decorada.
- una vez que se crea una instancia de la clase decorada, no se puede modificar (congelada)
- equipa la clase decorada con un atributo (modificable al decorar) fields_sep
- equipa la clase decorada con un método para imprimir como cadena el valor de parámetros definidos al instanciar la clase decorada, y no los añadidos por el decorador (como fields_sep)

Cumple con mis requisitos (puedo usar los objetos creados a partir de la clase decorada) como claves para un dict o sorted dict.

En caso de que pueda ayudar, aquí está.

PS: si tienes algún consejo o mejores prácticas, los agradezco.

from dataclasses import dataclass, asdict

<h1>for optional arguments in decorator: https://stackoverflow.com/a/24617244/4442753</h1>

def indexer(index_class=None, *, fields_sep:str='.'):
    def tweak(index_class):
        # Wrapp with @dataclass.
        index_class = dataclass(index_class, order=True, frozen=True)
        # Copy of original __init__ to call it without recursion.
        index_class_init = index_class.__init__

    def __init__(self,*args, **kws):
        object.__setattr__(self, "fields_sep", fields_sep)
        index_class_init(self, *args, **kws)

    index_class.__init__ = __init__

    def _to_str(self):
        return self.fields_sep.join(map(str, asdict(self).values()))

    index_class._to_str = property(_to_str)

    return index_class
if index_class:
    # Calling decorator without other parameters.
    return tweak(index_class)
# Calling decorator with other parameters.
return tweak

Prueba sin parámetros.

@indexer
class Test:
mu : int
nu : str
test = Test(3, 'oh')
assert test.to_str == '3.oh'

Prueba con el parámetro 'fields_sep'.

@indexer(fields_sep='-')
class Test:
mu : int
nu : str
test = Test(3, 'oh')
assert test.to_str == '3-oh'

Prueba de igualdad.

@indexer
class Test:
mu : int
nu : str
test1 = Test(3, 'oh')
test2 = Test(3, 'oh')
assert test1 == test2
test3 = Test(4, 'oh')
assert test1 != test3

Prueba dict.

di = {test1:[1,2,3], test2:[7,8,9]}
assert len(di) == 1
assert di[Test(3, 'oh')] == [7,8,9]

Tags:  , ,

Answer

  1. Avatar for davy.ai

    La propuesta de anidación de decoradores en el código proporcionado es aceptable y debería funcionar según lo previsto. El decorador indexer está diseñado para decorar primero la clase de entrada con dataclass y luego agregar el atributo id_ a la clase. A continuación, se copia el método __init__ original de la clase de entrada y se modifica para incluir el nuevo atributo id_.

    El decorador indexer está diseñado para ser llamado como una función, y este es un enfoque común en Python para decoradores con argumentos. No debería haber efectos secundarios al llamar a la función del decorador de esta manera.

    Con respecto a si el enfoque de decorador o herencia es mejor para agregar atributos y métodos a una clase, la respuesta depende de los requisitos específicos y el diseño de la clase. En este caso, el enfoque de decorador parece proporcionar la flexibilidad necesaria y también permite que la clase se instancie como una dataclass con las características deseadas.

Comments are closed.