¿Cómo evito el "yo".x = x; yo.y = y; yo.z = z " patrón en init?


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 su lugar?

Author: Wilfredo Sánchez Vega, 2016-02-04

11 answers

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__))
 89
Author: Siphor,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-02-12 08:01:00

EDITAR

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

RESPUESTA ORIGINAL

No hay realmente nada malo en copiar explícitamente parámetros en atributo. 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 un menor número de objetos. Otras veces, es necesario y no hay nada malo en ello. De todos modos, hacerlo explícitamente es el camino a seguir.

Sin embargo, ya que usted está preguntando 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
 104
Author: gruszczy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-02-05 03:10:45

Como otros han mencionado, la repetición no es mala, pero en algunos casos un namedtuple puede ser una gran opción para este tipo de problema. Esto evita usar locals () o kwargs, que suelen ser 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 un namedtuple como con cualquier otro objeto (el ejemplo continúa):

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()
 29
Author: A Small Shell Script,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-02-04 02:53:43

Lo explícito es mejor que lo 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 usted?

... dicho esto, si desea una tupla con nombre, le recomendaría usar una tupla con nombre (recuerde que las tuplas tienen ciertas condiciones asociadas a ellas)... tal vez quieras un decreto o incluso un dictado...

 28
Author: Joran Beasley,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-02-29 11:36:23

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 errores tipográficos al construir un objeto
  • funciona bien con la subclase (solo puede 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 da control sobre el orden en el 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, por lo que no me preocupa ninguna forma de saneamiento de entrada. Tal vez un AttributeError, sería más apropiado.

 20
Author: gerrit,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-02-13 11:17:43

También podrías hacer:

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

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

 10
Author: zondo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-02-04 01:35:38

Se trata de una solución sin importaciones adicionales.

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)

Aplicación

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 quieres cambiar locals() usa 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)
 8
Author: Mike Müller,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-12-11 03:08:53

Mi 0.02$. Está muy cerca de Joran Beasley respuesta, 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 a mi gusto) se puede reducir con esta técnica:

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

Y la llamada justa auto_init(locals()) de su __init__

 5
Author: bgusach,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-02-11 08:21:22

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

import attr

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

Ya no necesitas un método __init__, a menos que también haga otras cosas. Aquí está una introducción agradable por Glyph Lefkowitz.

 5
Author: RafG,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-01-06 10:04:24

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

 3
Author: Peter Krumins,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-02-21 01:35:46

Python 3.7 en adelante

En Python 3.7, puede (ab)utilizar el dataclass decorador, disponible en el módulo 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 el 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

Añadirá, entre otras cosas, un __init__() que se parece a:

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 InventoryItem mostrada anteriormente.

Si su clase es grande y compleja, puede ser inapropiado usar un dataclass. Estoy escribiendo 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.

 1
Author: gerrit,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-06-27 14:03:24