¿Cómo sobrecargar el método init basado en el tipo de argumento?


Digamos que tengo una clase que tiene un miembro llamado data que es una lista.

Quiero poder inicializar la clase con, por ejemplo, un nombre de archivo (que contiene datos para inicializar la lista) o con una lista real.

¿Cuál es tu técnica para hacer esto?

¿Simplemente comprueba el tipo mirando __class__?

¿Hay algún truco que me esté perdiendo?

Estoy acostumbrado a C++ donde la sobrecarga por tipo de argumento es fácil.

Author: martineau, 2008-09-26

10 answers

Una forma mucho más ordenada de obtener 'constructores alternativos' es usar classmethods. Por ejemplo:

>>> class MyData:
...     def __init__(self, data):
...         "Initialize MyData from a sequence"
...         self.data = data
...     
...     @classmethod
...     def fromfilename(cls, filename):
...         "Initialize MyData from a file"
...         data = open(filename).readlines()
...         return cls(data)
...     
...     @classmethod
...     def fromdict(cls, datadict):
...         "Initialize MyData from a dict's items"
...         return cls(datadict.items())
... 
>>> MyData([1, 2, 3]).data
[1, 2, 3]
>>> MyData.fromfilename("/tmp/foobar").data
['foo\n', 'bar\n', 'baz\n']
>>> MyData.fromdict({"spam": "ham"}).data
[('spam', 'ham')]

La razón por la que es más ordenado es que no hay duda sobre qué tipo se espera, y no se ve obligado a adivinar lo que la persona que llama pretendía que hiciera con el tipo de datos que le dio. El problema con isinstance(x, basestring) es que no hay manera de que el llamante le diga, por ejemplo, que aunque el tipo no es una banda base, debe tratarlo como una cadena (y no como otra secuencia.) Y tal vez a la persona que llama le gustaría usar el mismo tipo para diferentes propósitos, a veces como un solo elemento, y a veces como una secuencia de elementos. Ser explícito elimina toda duda y conduce a un código más robusto y más claro.

 360
Author: Thomas Wouters,
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
2008-09-26 20:42:57

Excelente pregunta. También he abordado este problema, y aunque estoy de acuerdo en que las "fábricas" (constructores de métodos de clase) son un buen método, me gustaría sugerir otro, que también me ha parecido muy útil:

Aquí hay una muestra (este es un método read y no un constructor, pero la idea es la misma):

def read(self, str=None, filename=None, addr=0):
    """ Read binary data and return a store object. The data
        store is also saved in the interal 'data' attribute.

        The data can either be taken from a string (str 
        argument) or a file (provide a filename, which will 
        be read in binary mode). If both are provided, the str 
        will be used. If neither is provided, an ArgumentError 
        is raised.
    """
    if str is None:
        if filename is None:
            raise ArgumentError('Please supply a string or a filename')

        file = open(filename, 'rb')
        str = file.read()
        file.close()
    ...
    ... # rest of code

La idea clave aquí es usar el excelente soporte de Python para argumentos con nombre para implementar esto. Ahora, si quiero leer los datos de un archivo, diga:

obj.read(filename="blob.txt")

Y para leerlo de una cadena, digo:

obj.read(str="\x34\x55")

De esta manera el usuario tiene un solo método para llamar. Manejarlo dentro, como usted vio, no es demasiado complejo

 31
Author: Eli Bendersky,
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
2010-07-05 13:01:09

Solución rápida y sucia

class MyData:
    def __init__(string=None,list=None):
        if string is not None:
            #do stuff
        elif list is not None:
            #do other stuff
        else:
            #make data empty

Entonces puedes llamarlo con

MyData(astring)
MyData(None, alist)
MyData()
 8
Author: Ben,
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
2012-04-18 21:38:33

Una mejor manera sería usar isinstance y type conversion. Si te entiendo bien, quieres esto:

def __init__ (self, filename):
    if isinstance (filename, basestring):
        # filename is a string
    else:
        # try to convert to a list
        self.path = list (filename)
 8
Author: John Millikin,
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
2014-07-01 06:05:06

Debe usar isinstance

isinstance(...)
    isinstance(object, class-or-type-or-tuple) -> bool

    Return whether an object is an instance of a class or of a subclass thereof.
    With a type as second argument, return whether that is the object's type.
    The form using a tuple, isinstance(x, (A, B, ...)), is a shortcut for
    isinstance(x, A) or isinstance(x, B) or ... (etc.).
 4
Author: Moe,
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
2011-06-21 15:19:41

Con python3, puedes usar Implementando Despacho Múltiple con Anotaciones de Función como escribió Python Cookbook:

import time


class Date(metaclass=MultipleMeta):
    def __init__(self, year:int, month:int, day:int):
        self.year = year
        self.month = month
        self.day = day

    def __init__(self):
        t = time.localtime()
        self.__init__(t.tm_year, t.tm_mon, t.tm_mday)

Y funciona como:

>>> d = Date(2012, 12, 21)
>>> d.year
2012
>>> e = Date()
>>> e.year
2018
 4
Author: carton.swing,
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-04-20 07:47:05

Probablemente quieras la función incorporada isinstance:

self.data = data if isinstance(data, list) else self.parse(data)
 2
Author: Eli Courtwright,
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
2008-09-26 19:54:30

Mi solución preferida es:

class MyClass:
    _data = []
    __init__(self,data=None):
        # do init stuff
        if not data: return
        self._data = list(data) # list() copies the list, instead of pointing to it.

Luego invócalo con MyClass() o MyClass([1,2,3]).

Espero que eso ayude. ¡Feliz Programación!

 0
Author: Fydo,
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
2014-09-24 13:55:57

OK, genial. Acabo de juntar este ejemplo con una tupla, no un nombre de archivo, pero eso es fácil. Gracias a todos.

class MyData:
    def __init__(self, data):
        self.myList = []
        if isinstance(data, tuple):
            for i in data:
                self.myList.append(i)
        else:
            self.myList = data

    def GetData(self):
        print self.myList

A =[1,2]

B = (2,3)

C = MyData (a)

D = MyData (b)

C. getData ()

D. getData ()

[1, 2]

[2, 3]

 -1
Author: Baltimark,
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
2008-10-17 12:58:37

¿Por qué no vas aún más pitónico?

class AutoList:
def __init__(self, inp):
    try:                        ## Assume an opened-file...
        self.data = inp.read()
    except AttributeError:
        try:                    ## Assume an existent filename...
            with open(inp, 'r') as fd:
                self.data = fd.read()
        except:
            self.data = inp     ## Who cares what that might be?
 -2
Author: ankostis,
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
2014-05-01 19:49:07