441 Stimmen

Wie kann man die __init__-Methode anhand des Argumenttyps überladen?

Nehmen wir an, ich habe eine Klasse, die ein Mitglied namens data hat, das eine Liste ist.

Ich möchte die Klasse z. B. mit einem Dateinamen (der Daten zur Initialisierung der Liste enthält) oder mit einer tatsächlichen Liste initialisieren können.

Wie gehen Sie dabei vor?

Prüfen Sie den Typ einfach anhand der __class__ ?

Gibt es einen Trick, den ich übersehen habe?

Ich bin an C++ gewöhnt, wo das Überladen nach Argumenttyp einfach ist.

574voto

Thomas Wouters Punkte 124421

Ein viel ordentlicherer Weg, um "alternative Konstruktoren" zu erhalten, ist die Verwendung von Klassenmethoden. Zum Beispiel:

>>> 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')]

Der Grund dafür ist, dass es keinen Zweifel darüber gibt, welcher Typ erwartet wird, und dass Sie nicht raten müssen, was der Aufrufer mit dem Datentyp, den er Ihnen gegeben hat, zu tun beabsichtigt. Das Problem mit isinstance(x, basestring) ist, dass es für den Aufrufer keine Möglichkeit gibt, Ihnen beispielsweise mitzuteilen, dass Sie den Typ als String (und nicht als eine andere Sequenz) behandeln sollen, obwohl es sich nicht um einen Basisring handelt. Und vielleicht möchte der Aufrufer denselben Typ für verschiedene Zwecke verwenden, manchmal als einzelnes Element und manchmal als eine Sequenz von Elementen. Eine eindeutige Formulierung beseitigt alle Zweifel und führt zu robusterem und klarerem Code.

50voto

carton.swing Punkte 1187

Mit python3 können Sie Implementierung der Mehrfachabfertigung mit Funktionsanmerkungen wie Python Cookbook schrieb:

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)

und das funktioniert so:

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

41voto

Eli Bendersky Punkte 246100

Eine ausgezeichnete Frage. Ich habe mich auch mit diesem Problem befasst, und obwohl ich zustimme, dass "Fabriken" (Klassen-Methoden-Konstruktoren) eine gute Methode sind, möchte ich eine andere vorschlagen, die ich ebenfalls sehr nützlich finde:

Hier ist ein Beispiel (dies ist ein read Methode und nicht ein Konstruktor, aber die Idee ist die gleiche):

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

Der Schlüsselgedanke ist hier die Verwendung von Pythons ausgezeichneter Unterstützung für benannte Argumente, um dies zu implementieren. Wenn ich nun die Daten aus einer Datei lesen möchte, sage ich:

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

Und um es von einer Zeichenkette abzulesen, sage ich:

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

Auf diese Weise hat der Benutzer nur eine einzige Methode aufzurufen. Der Umgang damit ist, wie Sie gesehen haben, nicht übermäßig komplex

15voto

Ben Punkte 661

Schnelle und schmutzige Lösung

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

Dann können Sie es mit

MyData(astring)
MyData(None, alist)
MyData()

10voto

John Millikin Punkte 190278

Ein besserer Weg wäre die Verwendung von isinstance und Typkonvertierung. Wenn ich dich richtig verstehe, willst du das:

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

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X