Best Practices für Python Factory-Funktionen

30

Angenommen, ich habe eine Datei foo.pymit einer Klasse Foo:

class Foo(object):
   def __init__(self, data):
      ...

Jetzt möchte ich eine Funktion hinzufügen, die ein FooObjekt auf eine bestimmte Weise aus Rohdaten erstellt. Sollte ich es als statische Methode in Foo oder als eine andere separate Funktion setzen?

class Foo(object):
   def __init__(self, data):
      ...
# option 1:
   @staticmethod
   def fromSourceData(sourceData):
      return Foo(processData(sourceData))

# option 2:
def makeFoo(sourceData):
   return Foo(processData(sourceData))

Ich weiß nicht, ob es wichtiger ist, für Benutzer bequem zu sein:

foo1 = foo.makeFoo(sourceData)

oder ob es wichtiger ist, eine klare Kopplung zwischen der Methode und der Klasse aufrechtzuerhalten:

foo1 = foo.Foo.fromSourceData(sourceData)
Jason S
quelle

Antworten:

45

Die Wahl sollte stattdessen zwischen einer Werksfunktion und einer dritten Option erfolgen. die Klassenmethode:

class Foo(object):
   def __init__(self, data):
      ...

   @classmethod
   def fromSourceData(klass, sourceData):
      return klass(processData(sourceData))

Klassenmethodenfabriken haben den zusätzlichen Vorteil, dass sie vererbbar sind. Sie können jetzt eine Unterklasse von erstellen Foo, und die geerbte Factory-Methode würde dann Instanzen dieser Unterklasse erzeugen.

Mit der Wahl zwischen einer Funktion und einer Klassenmethode würde ich die Klassenmethode wählen. Es dokumentiert ziemlich klar, welche Art von Objekt die Fabrik produzieren wird, ohne dass dies im Funktionsnamen explizit angegeben werden muss. Dies wird viel deutlicher, wenn Sie nicht nur mehrere Factory-Methoden, sondern auch mehrere Klassen haben.

Vergleichen Sie die folgenden zwei Alternativen:

foo.Foo.fromFrobnar(...)
foo.Foo.fromSpamNEggs(...)
foo.Foo.someComputedVersion()
foo.Bar.fromFrobnar(...)
foo.Bar.someComputedVersion()

gegen

foo.createFooFromFrobnar()
foo.createFooFromSpamNEggs()
foo.createSomeComputedVersionFoo()
foo.createBarFromFrobnar()
foo.createSomeComputedVersionBar()

Noch besser ist, dass Ihr Endbenutzer nur die FooKlasse importieren und sie für die verschiedenen Arten der Erstellung verwenden kann, da Sie alle Factory-Methoden an einem Ort haben:

from foo import Foo
Foo()
Foo.fromFrobnar(...)
Foo.fromSpamNEggs(...)
Foo.someComputedVersion()

Das stdlib- datetimeModul verwendet Klassenmethodenfactorys in großem Umfang, und die API ist dafür umso deutlicher.

Martijn Pieters
quelle
Gute Antwort. Weitere Informationen finden@classmethod Sie unter Bedeutung von @classmethod und @staticmethod für Anfänger.
Nealmcb
Großer Fokus auf Endbenutzerfunktionalität
drtf
1

In Python bevorzuge ich normalerweise eine Klassenhierarchie gegenüber Factory-Methoden. So etwas wie:

class Foo(object):
    def __init__(self, data):
        pass

    def method(self, param):
        pass

class SpecialFoo(Foo):
    def __init__(self, param1, param2):
        # Some processing.
        super().__init__(data)

class FromFile(Foo):
    def __init__(self, path):
        # Some processing.
        super().__init__(data)

Ich werde die gesamte Hierarchie (und nur diese) in ein Modul einfügen foos.py. Die Basisklasse Fooimplementiert normalerweise alle Methoden (und als solche die Schnittstelle) und wird normalerweise nicht direkt von den Benutzern erstellt. Die Unterklassen dienen dazu, den Basiskonstruktor zu überschreiben und anzugeben, wie Fooer erstellt werden soll. Dann würde ich einen erstellen, Fooindem ich den entsprechenden Konstruktor aufrufe, wie:

foo1 = mypackage.foos.SpecialFoo(param1, param2)
foo2 = mypackage.foos.FromFile('/path/to/foo')

Ich finde es eleganter und flexibler als Fabriken, die aus statischen Methoden, Klassenmethoden oder Funktionen auf Modulebene aufgebaut sind.

mdeff
quelle