tm1 attrs python python-2.7 python-decorators namedtuple python-attrs

python - attrs - ¿Cómo evito el "self.x=x; self.y=y; self.z=z ”patrón en__init__?



attrs python (11)

Python 3.7 en adelante

En Python 3.7, puede (ab) usar el decorador de dataclasses , disponible en el módulo de dataclasses . De la documentación:

Este módulo proporciona un decorador y funciones para agregar automáticamente métodos especiales generados como __init__() y __repr__() a las clases definidas por el usuario. Fue descrito originalmente en PEP 557.

Las variables miembro a usar en estos métodos generados se definen usando anotaciones de tipo PEP 526. Por ejemplo este código:

@dataclass class InventoryItem: ''''''Class for keeping track of an item in inventory.'''''' name: str unit_price: float quantity_on_hand: int = 0 def total_cost(self) -> float: return self.unit_price * self.quantity_on_hand

Agregará, entre otras cosas, un __init__() que se ve así:

def __init__(self, name: str, unit_price: float, quantity_on_hand: int=0): self.name = name self.unit_price = unit_price self.quantity_on_hand = quantity_on_hand

Tenga en cuenta que este método se agrega automáticamente a la clase: no se especifica directamente en la definición de InventoryItem que se muestra arriba.

Si su clase es grande y compleja, puede ser inapropiado usar una dataclass . Escribo esto el día del lanzamiento de Python 3.7.0, por lo que los patrones de uso aún no están bien establecidos.

Veo patrones como

def __init__(self, x, y, z): ... self.x = x self.y = y self.z = z ...

con bastante frecuencia, a menudo con muchos más parámetros. ¿Hay una buena manera de evitar este tipo de repetitividad tediosa? ¿Debería la clase heredar de namedtuple en namedtuple lugar?


Como otros han mencionado, la repetición no es mala, pero en algunos casos una tupla con nombre puede ser una gran opción para este tipo de problema. Esto evita el uso de locales () o kwargs, que generalmente son una mala idea.

from collections import namedtuple # declare a new object type with three properties; x y z # the first arg of namedtuple is a typename # the second arg is comma-separated or space-separated property names XYZ = namedtuple("XYZ", "x, y, z") # create an object of type XYZ. properties are in order abc = XYZ("one", "two", 3) print abc.x print abc.y print abc.z

He encontrado un uso limitado para él, pero puede heredar una tupla con nombre como con cualquier otro objeto (ejemplo continuado):

class MySuperXYZ(XYZ): """ I add a helper function which returns the original properties """ def properties(self): return self.x, self.y, self.z abc2 = MySuperXYZ(4, "five", "six") print abc2.x print abc2.y print abc2.z print abc2.properties()


Editar: si tiene Python 3.7+, simplemente use dataclasses

Una solución decoradora que mantiene la firma:

import decorator import inspect import sys @decorator.decorator def simple_init(func, self, *args, **kws): """ @simple_init def __init__(self,a,b,...,z) dosomething() behaves like def __init__(self,a,b,...,z) self.a = a self.b = b ... self.z = z dosomething() """ #init_argumentnames_without_self = [''a'',''b'',...,''z''] if sys.version_info.major == 2: init_argumentnames_without_self = inspect.getargspec(func).args[1:] else: init_argumentnames_without_self = tuple(inspect.signature(func).parameters.keys())[1:] positional_values = args keyword_values_in_correct_order = tuple(kws[key] for key in init_argumentnames_without_self if key in kws) attribute_values = positional_values + keyword_values_in_correct_order for attribute_name,attribute_value in zip(init_argumentnames_without_self,attribute_values): setattr(self,attribute_name,attribute_value) # call the original __init__ func(self, *args, **kws) class Test(): @simple_init def __init__(self,a,b,c,d=4): print(self.a,self.b,self.c,self.d) #prints 1 3 2 4 t = Test(1,c=2,b=3) #keeps signature #prints [''self'', ''a'', ''b'', ''c'', ''d''] if sys.version_info.major == 2: print(inspect.getargspec(Test.__init__).args) else: print(inspect.signature(Test.__init__))


Es una forma natural de hacer cosas en Python. No intentes inventar algo más inteligente, conducirá a un código demasiado inteligente que nadie en tu equipo entenderá. Si quieres ser un jugador de equipo y luego seguir escribiéndolo de esta manera.


Esta es una solución sin ninguna importación adicional.

Función auxiliar

Una pequeña función auxiliar lo hace más conveniente y reutilizable:

def auto_init(local_name_space): """Set instance attributes from arguments. """ self = local_name_space.pop(''self'') for name, value in local_name_space.items(): setattr(self, name, value)

Solicitud

Necesitas llamarlo con locals() :

class A: def __init__(self, x, y, z): auto_init(locals())

Prueba

a = A(1, 2, 3) print(a.__dict__)

Salida:

{''y'': 2, ''z'': 3, ''x'': 1}

Sin cambiar locals()

Si no le gusta cambiar locals() use esta versión:

def auto_init(local_name_space): """Set instance attributes from arguments. """ for name, value in local_name_space.items(): if name != ''self'': setattr(local_name_space[''self''], name, value)


Mi 0.02 $. Está muy cerca de la respuesta de Joran Beasley, pero más elegante:

def __init__(self, a, b, c, d, e, f): vars(self).update((k, v) for k, v in locals().items() if v is not self)

Además, la respuesta de Mike Müller (la mejor para mi gusto) se puede reducir con esta técnica:

def auto_init(ns): self = ns.pop(''self'') vars(self).update(ns)

Y simplemente llame a auto_init(locals()) desde su __init__


Para ampliar la respuesta de gruszczy , he usado un patrón como:

class X: x = None y = None z = None def __init__(self, **kwargs): for (k, v) in kwargs.items(): if hasattr(self, k): setattr(self, k, v) else: raise TypeError(''Unknown keyword argument: {:s}''.format(k))

Me gusta este método porque:

  • evita la repetición
  • es resistente a los errores tipográficos al construir un objeto
  • funciona bien con subclases (puede simplemente super().__init(...) )
  • permite la documentación de los atributos en un nivel de clase (donde pertenecen) en lugar de en X.__init__

Antes de Python 3.6, esto no proporciona control sobre el orden en que se establecen los atributos, lo que podría ser un problema si algunos atributos son propiedades con setters que acceden a otros atributos.

Probablemente podría mejorarse un poco, pero soy el único usuario de mi propio código, así que no me preocupa ninguna forma de desinfección de entrada. Quizás un AttributeError sería más apropiado.


También puedes hacer:

locs = locals() for arg in inspect.getargspec(self.__init__)[0][1:]: setattr(self, arg, locs[arg])

Por supuesto, tendría que importar el módulo de inspect .


Una biblioteca interesante que maneja esto (y evita muchas otras repeticiones) es attrs . Su ejemplo, por ejemplo, podría reducirse a esto (suponga que la clase se llama MyClass ):

import attr @attr.s class MyClass: x = attr.ib() y = attr.ib() z = attr.ib()

Ya ni siquiera necesita un método __init__ , a menos que también haga otras cosas. Aquí hay una buena introducción de Glyph Lefkowitz .


explícito es mejor que implícito ... así que seguro que podría hacerlo más conciso:

def __init__(self,a,b,c): for k,v in locals().items(): if k != "self": setattr(self,k,v)

La mejor pregunta es ¿debería?

... Dicho esto, si desea una tupla con nombre, recomendaría usar una tupla con nombre (recuerde que las tuplas tienen ciertas condiciones asociadas) ... tal vez desee una sentencia ordenada o incluso solo una sentencia ...


Descargo de responsabilidad: Parece que varias personas están preocupadas por presentar esta solución, por lo que proporcionaré un descargo de responsabilidad muy claro. No deberías usar esta solución. Solo lo proporciono como información, para que sepa que el idioma es capaz de esto. El resto de la respuesta es solo mostrar capacidades de lenguaje, no respaldar su uso de esta manera.

Realmente no hay nada de malo en copiar explícitamente los parámetros en los atributos. Si tiene demasiados parámetros en el ctor, a veces se considera un olor a código y tal vez debería agrupar estos parámetros en menos objetos. Otras veces, es necesario y no tiene nada de malo. De todos modos, hacerlo explícitamente es el camino a seguir.

Sin embargo, dado que usted pregunta CÓMO se puede hacer (y no si se debe hacer), entonces una solución es esta:

class A: def __init__(self, **kwargs): for key in kwargs: setattr(self, key, kwargs[key]) a = A(l=1, d=2) a.l # will return 1 a.d # will return 2