969 Stimmen

Was ist eine saubere "Python" Weg, um mehrere Konstruktoren zu implementieren?

Ich kann keine endgültige Antwort darauf finden. Soviel ich weiß, kann man nicht mehrere __init__ Funktionen in einer Python-Klasse. Wie kann ich dieses Problem also lösen?

Angenommen, ich habe eine Klasse namens Cheese mit dem number_of_holes Eigentum. Wie kann ich zwei Möglichkeiten zur Erstellung von Käseobjekten haben...

  1. Einer, der eine Reihe von Löchern wie dieses nimmt: parmesan = Cheese(num_holes = 15) .
  2. Und eine, die keine Argumente benötigt und nur die number_of_holes Eigentum: gouda = Cheese() .

Mir fällt nur eine Möglichkeit ein, dies zu tun, aber das erscheint mir umständlich:

class Cheese():
    def __init__(self, num_holes = 0):
        if (num_holes == 0):
            # Randomize number_of_holes
        else:
            number_of_holes = num_holes

Was sagen Sie dazu? Gibt es einen anderen Weg?

11 Stimmen

Ich denke init ist kein Konstruktor, sondern ein Initialisierer. neu wäre ein Konstrukteur

0 Stimmen

992voto

Ber Punkte 37347

Verwendung von num_holes=None als Standardeinstellung ist in Ordnung, wenn Sie nur __init__ .

Wenn Sie mehrere, unabhängige "Konstruktoren" wünschen, können Sie diese als Klassenmethoden bereitstellen. Diese werden normalerweise Fabrikmethoden genannt. In diesem Fall könnten Sie den Standard für num_holes sein 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))

Erstellen Sie nun ein Objekt wie dieses:

gouda = Cheese()
emmentaler = Cheese.random()
leerdammer = Cheese.slightly_holey()

0 Stimmen

+1 für das schöne Beispiel von @classmethod. Aber als Antwort auf die ursprüngliche Frage bevorzuge ich die akzeptierte Lösung, weil sie meiner Meinung nach eher in die Richtung geht, mehrere Konstruktoren zu haben (oder sie zu überladen, in anderen Sprachen).

51 Stimmen

@rmbianchi: Die akzeptierte Antwort entspricht vielleicht eher anderen Sprachen, aber sie ist auch weniger pythonisch: @classmethod s sind der pythonische Weg, um mehrere Konstruktoren zu implementieren.

0 Stimmen

@EthanFurman: Ich mag deinen Käse und danke für den verbesserten Code. Das ist wirklich besser, wenn man den __init__() Konstruktor direkt verwendet.

973voto

vartec Punkte 124396

Eigentlich None ist viel besser für "magische" Werte geeignet:

class Cheese():
    def __init__(self, num_holes = None):
        if num_holes is None:
            ...

Wenn Sie nun die völlige Freiheit haben wollen, weitere Parameter hinzuzufügen:

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

Zur besseren Erklärung des Konzepts der *args y **kwargs (Sie können diese Namen sogar ändern):

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

113 Stimmen

Für alle Interessierten, kwargs steht für Schlüsselwortargumente (erscheint logisch, wenn man es weiß) :)

58 Stimmen

Es gibt Momente, die *args y **kwargs sind ein Overkill. Bei den meisten Konstruktoren wollen Sie wissen, was Ihre Argumente sind.

1 Stimmen

@user989762 Ja! Auf jeden Fall!

81voto

Andrzej Pronobis Punkte 30692

Man sollte auf jeden Fall die bereits geposteten Lösungen vorziehen, aber da noch niemand diese Lösung erwähnt hat, denke ich, dass sie der Vollständigkeit halber erwähnt werden sollte.

Le site @classmethod kann geändert werden, um einen alternativen Konstruktor bereitzustellen, der nicht den Standardkonstruktor aufruft ( __init__ ). Stattdessen wird eine Instanz erstellt mit __new__ .

Dies könnte verwendet werden, wenn die Art der Initialisierung nicht auf der Grundlage des Typs des Konstruktorarguments ausgewählt werden kann und die Konstruktoren keinen gemeinsamen Code haben.

Beispiel:

class MyClass(set):

    def __init__(self, filename):
        self._value = load_from_file(filename)

    @classmethod
    def from_somewhere(cls, somename):
        obj = cls.__new__(cls)  # Does not call __init__
        super(MyClass, obj).__init__()  # Don't forget to call any polymorphic base class initializers
        obj._value = load_from_somewhere(somename)
        return obj

15 Stimmen

Dies ist die Lösung, die in der Tat unabhängige Konstruktoren bietet, anstatt sich mit __init__ die Argumente der Kommission. Könnten Sie jedoch bitte Hinweise darauf geben, dass diese Methode in irgendeiner Weise offiziell anerkannt oder unterstützt wird? Wie sicher und zuverlässig ist der direkte Aufruf von __new__ Methode?

1 Stimmen

Ich habe es so gemacht und bin dann hierher gekommen, um die obige Frage zu stellen, um zu sehen, ob mein Weg richtig war. Sie müssen trotzdem anrufen super Andernfalls wird dies in der kooperativen Mehrfachvererbung nicht funktionieren, daher habe ich die Zeile zu Ihrer Antwort hinzugefügt.

1 Stimmen

Ich frage mich, ob man einen Dekorator 'Konstruktor' definieren könnte (der die neu und super Sachen) und dann tun: @constructor def other_init(self, stuff): self.stuff = stuff

31voto

Yes - that Jake. Punkte 16190

Alle diese Antworten sind ausgezeichnet, wenn Sie optionale Parameter verwenden wollen, aber eine andere Pythonic-Möglichkeit ist die Verwendung einer classmethod, um einen Pseudokonstruktor im Stil einer Fabrik zu erzeugen:

def __init__(self, num_holes):

  # do stuff with the number

@classmethod
def fromRandom(cls):

  return cls( # some-random-number )

21voto

Ferdinand Beyer Punkte 61121

Warum denken Sie, dass Ihre Lösung "klobig" ist? Ich persönlich würde in Situationen wie der Ihren einen Konstruktor mit Standardwerten mehreren überladenen Konstruktoren vorziehen (Python unterstützt ohnehin keine Methodenüberladung):

def __init__(self, num_holes=None):
    if num_holes is None:
        # Construct a gouda
    else:
        # custom cheese
    # common initialization

Für wirklich komplexe Fälle mit vielen verschiedenen Konstruktoren ist es vielleicht sinnvoller, stattdessen verschiedene Fabrikfunktionen zu verwenden:

@classmethod
def create_gouda(cls):
    c = Cheese()
    # ...
    return c

@classmethod
def create_cheddar(cls):
    # ...

In Ihrem Käse-Beispiel sollten Sie aber vielleicht eine Gouda-Unterklasse von Cheese verwenden...

7 Stimmen

Werksfunktionen verwenden cls : verwenden cls anstelle von Käse . Wenn nicht, welchen Sinn macht es dann, Klassenmethoden anstelle von statischen Methoden zu verwenden?

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