sobrecarga decoradores constructores python constructor

constructores - decoradores python 3



¿Qué es una forma limpia y pitónica de tener varios constructores en Python? (11)

No puedo encontrar una respuesta definitiva para esto. AFAIK, no puedes tener múltiples funciones __init__ en una clase de Python. Entonces, ¿cómo resuelvo este problema?

Supongamos que tengo una clase llamada Cheese con la propiedad number_of_holes . ¿Cómo puedo tener dos formas de crear objetos de queso ...

  1. uno que tiene una serie de agujeros como este: parmesan = Cheese(num_holes = 15)
  2. y uno que no toma argumentos y simplemente asigna al azar la propiedad number_of_holes : gouda = Cheese()

Puedo pensar en una sola manera de hacer esto, pero eso parece un poco torpe:

class Cheese(): def __init__(self, num_holes = 0): if (num_holes == 0): # randomize number_of_holes else: number_of_holes = num_holes

¿Qué dices? ¿Hay otra manera?


¿Por qué crees que tu solución es "torpe"? Personalmente, preferiría un constructor con valores predeterminados sobre varios constructores sobrecargados en situaciones como la suya (de todos modos Python no admite la sobrecarga de métodos):

def __init__(self, num_holes=None): if num_holes is None: # Construct a gouda else: # custom cheese # common initialization

Para casos realmente complejos con muchos constructores diferentes, podría ser más limpio usar diferentes funciones de fábrica en su lugar:

@classmethod def create_gouda(cls): c = Cheese() # ... return c @classmethod def create_cheddar(cls): # ...

En su ejemplo de queso, es posible que desee utilizar una subclase de queso Gouda ...


Así es como lo resolví para una clase de YearQuarter que tuve que crear. __init__ un __init__ con un solo parámetro llamado value . El código para __init__ simplemente decide qué tipo de parámetro de value es y procesa los datos en consecuencia. En caso de que desee varios parámetros de entrada, simplemente los empaca en una sola tupla y prueba que el value sea ​​una tupla.

Lo usas así:

>>> temp = YearQuarter(datetime.date(2017, 1, 18)) >>> print temp 2017-Q1 >>> temp = YearQuarter((2017, 1)) >>> print temp 2017-Q1

Y así es como se ve __init__ y el resto de la clase:

import datetime class YearQuarter: def __init__(self, value): if type(value) is datetime.date: self._year = value.year self._quarter = (value.month + 2) / 3 elif type(value) is tuple: self._year = int(value[0]) self._quarter = int(value[1]) def __str__(self): return ''{0}-Q{1}''.format(self._year, self._quarter)

Puede expandir el __init__ con varios mensajes de error, por supuesto. Los omití para este ejemplo.


Definitivamente, uno debería preferir las soluciones ya publicadas, pero como nadie mencionó esta solución todavía, creo que vale la pena mencionarlo para completar.

El enfoque de @classmethod se puede modificar para proporcionar un constructor alternativo que no invoque al constructor predeterminado ( __init__ ). En su lugar, se crea una instancia utilizando __new__ .

Esto podría usarse si el tipo de inicialización no se puede seleccionar en función del tipo del argumento del constructor, y los constructores no comparten el código.

Ejemplo:

class MyClass(set): def __init__(self, filename): self._value = load_from_file(filename) @classmethod def from_somewhere(cls, somename): obj = cls.__new__(cls) # Does not call __init__ obj._value = load_from_somewhere(somename) return obj


En realidad, None es mucho mejor para los valores "mágicos":

class Cheese(): def __init__(self, num_holes = None): if num_holes is None: ...

Ahora si quieres libertad total para agregar más parámetros:

class Cheese(): def __init__(self, *args, **kwargs): #args -- tuple of anonymous arguments #kwargs -- dictionary of named arguments self.num_holes = kwargs.get(''num_holes'',random_holes())

Para explicar mejor el concepto de *args y **kwargs (puedes cambiar estos nombres):

def f(*args, **kwargs): print ''args: '', args, '' kwargs: '', kwargs >>> f(''a'') args: (''a'',) kwargs: {} >>> f(ar=''a'') args: () kwargs: {''ar'': ''a''} >>> f(1,2,param=3) args: (1, 2) kwargs: {''param'': 3}

http://docs.python.org/reference/expressions.html#calls


Esas son buenas ideas para su implementación, pero si está presentando una interfaz para hacer queso a un usuario. No les importa cuántos agujeros tiene el queso o qué partes internas se utilizan para hacer queso. El usuario de su código solo quiere "gouda" o "parmesean", ¿verdad?

Entonces, ¿por qué no hacer esto?

# cheese_user.py from cheeses import make_gouda, make_parmesean gouda = make_gouda() paremesean = make_parmesean()

Y luego puede usar cualquiera de los métodos anteriores para implementar las funciones:

# cheeses.py class Cheese(object): def __init__(self, *args, **kwargs): #args -- tuple of anonymous arguments #kwargs -- dictionary of named arguments self.num_holes = kwargs.get(''num_holes'',random_holes()) def make_gouda(): return Cheese() def make_paremesean(): return Cheese(num_holes=15)

Esta es una buena técnica de encapsulación, y creo que es más Pythonic. Para mí, esta forma de hacer las cosas encaja más en línea con la escritura de pato. Simplemente estás pidiendo un objeto Gouda y no te importa qué clase es.


La mejor respuesta es la que aparece arriba sobre los argumentos predeterminados, pero me divertí escribiendo esto, y ciertamente encaja en la factura de "constructores múltiples". Úselo bajo su propio riesgo.

¿Qué pasa con el new método.

"Las implementaciones típicas crean una nueva instancia de la clase invocando el método new () de la superclase usando super (currentclass, cls). New (cls [, ...]) con los argumentos apropiados y luego modificando la instancia recién creada según sea necesario antes devolviéndolo ".

Por lo tanto, puede hacer que el nuevo método modifique la definición de su clase adjuntando el método constructor apropiado.

class Cheese(object): def __new__(cls, *args, **kwargs): obj = super(Cheese, cls).__new__(cls) num_holes = kwargs.get(''num_holes'', random_holes()) if num_holes == 0: cls.__init__ = cls.foomethod else: cls.__init__ = cls.barmethod return obj def foomethod(self, *args, **kwargs): print "foomethod called as __init__ for Cheese" def barmethod(self, *args, **kwargs): print "barmethod called as __init__ for Cheese" if __name__ == "__main__": parm = Cheese(num_holes=5)


Todas estas respuestas son excelentes si desea usar parámetros opcionales, pero otra posibilidad de Pythonic es usar un método de clase para generar un pseudo-constructor de estilo de fábrica:

def __init__(self, num_holes): # do stuff with the number @classmethod def fromRandom(cls): return cls( # some-random-number )


Usar num_holes=None como predeterminado está bien si va a tener solo __init__ .

Si desea "constructores" múltiples e independientes, puede proporcionarlos como métodos de clase. Estos son generalmente llamados métodos de fábrica. En este caso, podría tener el valor predeterminado para num_holes ser 0 .

class Cheese(object): def __init__(self, num_holes=0): "defaults to a solid cheese" self.number_of_holes = num_holes @classmethod def random(cls): return cls(randint(0, 100)) @classmethod def slightly_holey(cls): return cls(randint((0,33)) @classmethod def very_holey(cls): return cls(randint(66, 100))

Ahora crea un objeto como este:

gouda = Cheese() emmentaler = Cheese.random() leerdammer = Cheese.slightly_holey()


Use num_holes=None como predeterminado, en su lugar. Luego verifique si num_holes is None , y si es así, seleccione aleatoriamente. Eso es lo que generalmente veo, de todos modos.

Los métodos de construcción más radicalmente diferentes pueden justificar un método de clase que devuelve una instancia de cls .


Yo usaría la herencia. Especialmente si va a haber más diferencias que número de agujeros. Especialmente si Gouda necesitará tener un conjunto diferente de miembros, entonces Parmesan.

class Gouda(Cheese): def __init__(self): super(Gouda).__init__(num_holes=10) class Parmesan(Cheese): def __init__(self): super(Parmesan).__init__(num_holes=15)


class Cheese: def __init__(self, *args, **kwargs): """A user-friendly initialiser for the general-purpose constructor. """ ... def _init_parmesan(self, *args, **kwargs): """A special initialiser for Parmesan cheese. """ ... def _init_gauda(self, *args, **kwargs): """A special initialiser for Gauda cheese. """ ... @classmethod def make_parmesan(cls, *args, **kwargs): new = cls.__new__(cls) new._init_parmesan(*args, **kwargs) return new @classmethod def make_gauda(cls, *args, **kwargs): new = cls.__new__(cls) new._init_gauda(*args, **kwargs) return new