decoradores - sobrecarga de constructores python




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

¿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 ...

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?


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.


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.


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()

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






constructor