web-dev-qa-db-fra.com

Comment surcharger la méthode __init__ en fonction du type d'argument?

Disons que j'ai une classe qui a un membre appelé data qui est une liste.

Je veux pouvoir initialiser la classe avec, par exemple, un nom de fichier (qui contient des données pour initialiser la liste) ou avec une liste réelle.

Quelle est votre technique pour faire ça?

Est-ce que vous vérifiez le type en regardant __class__?

Y a-t-il une astuce qui pourrait me manquer?

Je suis habitué au C++ où la surcharge par type d'argument est facile.

293
Baltimark

Une méthode beaucoup plus simple pour obtenir des "constructeurs alternatifs" consiste à utiliser des méthodes de classe. Par exemple:

>>> 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 raison pour laquelle il est plus judicieux est qu'il n'y a aucun doute sur le type attendu, et vous n'êtes pas obligé de deviner ce que l'appelant vous a demandé de faire avec le type de données qu'il vous a fourni. Le problème avec isinstance(x, basestring) est qu’il n’ya aucun moyen pour l’appelant de vous dire, par exemple, que même si le type n’est pas une chaîne de base, vous devez le traiter comme une chaîne (et non une autre séquence). l'appelant souhaite utiliser le même type à des fins différentes, parfois sous la forme d'un élément unique et parfois sous la forme d'une séquence d'éléments. Etre explicite enlève tout doute et conduit à un code plus robuste et plus clair.

401
Thomas Wouters

Excellente question. J'ai également abordé ce problème et bien que je convienne que les "usines" (constructeurs de méthodes de classes) sont une bonne méthode, je voudrais en suggérer une autre, que j'ai également trouvée très utile:

Voici un exemple (c'est une méthode read et non un constructeur, mais l'idée est la même):

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

L'idée principale est d'utiliser l'excellent support de Python pour les arguments nommés afin de l'implémenter. Maintenant, si je veux lire les données d'un fichier, je dis:

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

Et pour le lire d'une chaîne, je dis:

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

De cette façon, l'utilisateur n'a qu'une seule méthode à appeler. Le manipuler à l'intérieur, comme vous l'avez vu, n'est pas trop complexe

33
Eli Bendersky

Solution rapide et sale

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

Ensuite, vous pouvez l'appeler avec

MyData(astring)
MyData(None, alist)
MyData()
10
Ben

Un meilleur moyen serait d'utiliser la conversion de type et d'instance. Si je vous comprends bien, vous voulez ceci:

def __init__ (self, filename):
    if isinstance (filename, basestring):
        # filename is a string
    else:
        # try to convert to a list
        self.path = list (filename)
8
John Millikin

avec python3, vous pouvez utiliser implémentation de plusieurs messages avec des annotations de fonctions comme Python Cookbook a écrit:

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)

et cela fonctionne comme:

>>> d = Date(2012, 12, 21)
>>> d.year
2012
>>> e = Date()
>>> e.year
2018
6
carton.swing

Vous devriez utiliser 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
Moe

Vous voulez probablement la fonction intégrée isinstance:

self.data = data if isinstance(data, list) else self.parse(data)
2
Eli Courtwright

Ma solution préférée est:

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.

Invoquez-le ensuite avec MyClass() ou MyClass([1,2,3]).

J'espère que ça t'as aidé. Bon codage!

1
Fydo