python constructor initialization initializer idiomatic

python - Múltiples constructores: ¿la manera pitónica?



initialization initializer (7)

Esta pregunta ya tiene una respuesta aquí:

Tengo una clase contenedor que contiene datos. Cuando se crea el contenedor, existen diferentes métodos para pasar datos.

  1. Pase un archivo que contiene los datos
  2. Pase los datos directamente a través de argumentos
  3. No pase datos; solo crea un contenedor vacío

En Java, crearía tres constructores. Así es como se vería si fuera posible en Python:

class Container: def __init__(self): self.timestamp = 0 self.data = [] self.metadata = {} def __init__(self, file): f = file.open() self.timestamp = f.get_timestamp() self.data = f.get_data() self.metadata = f.get_metadata() def __init__(self, timestamp, data, metadata): self.timestamp = timestamp self.data = data self.metadata = metadata

En Python, veo tres soluciones obvias, pero ninguna de ellas es bonita:

A : Uso de argumentos de palabras clave:

def __init__(self, **kwargs): if ''file'' in kwargs: ... elif ''timestamp'' in kwargs and ''data'' in kwargs and ''metadata'' in kwargs: ... else: ... create empty container

B : Uso de argumentos predeterminados:

def __init__(self, file=None, timestamp=None, data=None, metadata=None): if file: ... elif timestamp and data and metadata: ... else: ... create empty container

C : solo proporcione un constructor para crear contenedores vacíos. Proporcione métodos para llenar contenedores con datos de diferentes fuentes.

def __init__(self): self.timestamp = 0 self.data = [] self.metadata = {} def add_data_from_file(file): ... def add_data(timestamp, data, metadata): ...

Las soluciones A y B son básicamente las mismas. No me gusta hacer el if / else, especialmente porque tengo que verificar si se proporcionaron todos los argumentos necesarios para este método. A es un poco más flexible que B si alguna vez se va a extender el código mediante un cuarto método para agregar datos.

La solución C parece ser la mejor, pero el usuario debe saber qué método necesita. Por ejemplo: no puede hacer c = Container(args) si no sabe qué es args .

¿Cuál es la solución más pitónica?


¿Cuáles son los objetivos del sistema para este código? Desde mi punto de vista, su frase crítica es but the user has to know which method he requires. ¿Qué experiencia quieres que tengan tus usuarios con tu código? Eso debería impulsar el diseño de la interfaz.

Ahora, pase a la capacidad de mantenimiento: ¿qué solución es más fácil de leer y mantener? Nuevamente, siento que la solución C es inferior. Para la mayoría de los equipos con los que he trabajado, la solución B es preferible a A: es un poco más fácil de leer y comprender, aunque ambos se dividen fácilmente en pequeños bloques de código para el tratamiento.


La forma más pitónica es asegurarse de que los argumentos opcionales tengan valores predeterminados. Por lo tanto, incluya todos los argumentos que sabe que necesita y asígneles los valores predeterminados apropiados.

def __init__(self, timestamp=None, data=[], metadata={}): timestamp = time.now()

Una cosa importante para recordar es que cualquier argumento requerido no debe tener valores predeterminados, ya que desea que se genere un error si no están incluidos.

Puede aceptar incluso más argumentos opcionales usando *args y **kwargs al final de su lista de argumentos.

def __init__(self, timestamp=None, data=[], metadata={}, *args, **kwards): if ''something'' in kwargs: # do something


La mayoría de Pythonic sería lo que la biblioteca estándar de Python ya hace. El desarrollador principal Raymond Hettinger (el chico de las collections ) dio una charla sobre esto , además de pautas generales sobre cómo escribir clases.

Use funciones separadas de nivel de clase para inicializar instancias, como por ejemplo dict.fromkeys() no es el inicializador de clase pero aún devuelve una instancia de dict . Esto le permite ser flexible con los argumentos que necesita sin cambiar las firmas del método a medida que cambian los requisitos.


No estoy seguro si entendí bien, pero ¿no funcionaría esto?

def __init__(self, file=None, timestamp=0, data=[], metadata={}): if file: ... else: self.timestamp = timestamp self.data = data self.metadata = metadata

O incluso podrías hacer:

def __init__(self, file=None, timestamp=0, data=[], metadata={}): if file: # Implement get_data to return all the stuff as a tuple timestamp, data, metadata = f.get_data() self.timestamp = timestamp self.data = data self.metadata = metadata

Gracias al consejo de Jon Kiparsky, hay una mejor manera de evitar declaraciones globales sobre data y metadata así que esta es la nueva forma:

def __init__(self, file=None, timestamp=None, data=None, metadata=None): if file: # Implement get_data to return all the stuff as a tuple with open(file) as f: timestamp, data, metadata = f.get_data() self.timestamp = timestamp or 0 self.data = data or [] self.metadata = metadata or {}


No puede tener múltiples constructores, pero puede tener varios métodos de fábrica con nombres correctos.

class Document(object): def __init__(self, whatever args you need): """Do not invoke directly. Use from_NNN methods.""" # Implementation is likely a mix of A and B approaches. @classmethod def from_string(cls, string): # Do any necessary preparations, use the `string` return cls(...) @classmethod def from_json_file(cls, file_object): # Read and interpret the file as you want return cls(...) @classmethod def from_docx_file(cls, file_object): # Read and interpret the file as you want, differently. return cls(...) # etc.

Sin embargo, no puede evitar fácilmente que el usuario use el constructor directamente. (Si es crítico, como precaución de seguridad durante el desarrollo, puede analizar la pila de llamadas en el constructor y verificar que la llamada se realice a partir de uno de los métodos esperados).


No puede tener varios métodos con el mismo nombre en Python . La sobrecarga de funciones, a diferencia de Java , no es compatible.

Use los parámetros predeterminados o **kwargs y *args argumentos.

Puede crear métodos estáticos o métodos de clase con el decorador @staticmethod o @classmethod para devolver una instancia de su clase o para agregar otros constructores.

Te aconsejo que hagas:

class F: def __init__(self, timestamp=0, data=None, metadata=None): self.timestamp = timestamp self.data = list() if data is None else data self.metadata = dict() if metadata is None else metadata @classmethod def from_file(cls, path): _file = cls.get_file(path) timestamp = _file.get_timestamp() data = _file.get_data() metadata = _file.get_metadata() return cls(timestamp, data, metadata) @classmethod def from_metadata(cls, timestamp, data, metadata): return cls(timestamp, data, metadata) @staticmethod def get_file(path): # ... pass

⚠ Nunca tenga tipos mutables como predeterminados en python. ⚠ Mira here .


Si está en Python 3.4+, puede usar el decorador functools.singledispatch para hacer esto (con un poco de ayuda adicional del decorador de @ZeroPiraeus que @ZeroPiraeus escribió para su respuesta ):

class Container: @methoddispatch def __init__(self): self.timestamp = 0 self.data = [] self.metadata = {} @__init__.register(File) def __init__(self, file): f = file.open() self.timestamp = f.get_timestamp() self.data = f.get_data() self.metadata = f.get_metadata() @__init__.register(Timestamp) def __init__(self, timestamp, data, metadata): self.timestamp = timestamp self.data = data self.metadata = metadata