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