web-dev-qa-db-ja.com

pythonファクトリ関数のベストプラクティス

ファイルがあるとしますfoo.pyクラスを含むFoo

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

次に、生のソースデータから特定の方法でFooオブジェクトを作成する関数を追加します。 Fooの静的メソッドとして、または別の別の関数として配置する必要がありますか?

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

ユーザーにとって便利であることがより重要であるかどうかはわかりません。

foo1 = foo.makeFoo(sourceData)

または、メソッドとクラスの間の明確な結合を維持することがより重要かどうか:

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

代わりに、ファクトリ関数とthirdオプションを選択する必要があります。クラスメソッド:

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

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

Classmethodファクトリには、継承可能であるという追加の利点があります。これでFooのサブクラスを作成でき、継承されたファクトリメソッドはそのサブクラスのインスタンスを生成します。

関数とクラスメソッドのどちらかを選択すると、クラスメソッドを選択します。これは、ファクトリーが生成するオブジェクトのkindを明確に文書化します。これを関数名で明示する必要はありません。これは、複数のファクトリメソッドだけでなく、複数のクラスもある場合に、より明確になります。

次の2つの選択肢を比較してください。

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

vs.

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

さらに良いことに、エンドユーザーはFooクラスだけをインポートし、それをさまざまな方法で使用して、すべてのファクトリメソッドを1か所に置くことができます。

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

Stdlib datetime module はクラスメソッドファクトリを広く使用しており、そのAPIはより明確です。

46
Martijn Pieters

Pythonでは、通常、ファクトリーメソッドよりもクラス階層を好みます。何かのようなもの:

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)

階層全体(この階層のみ)をモジュールに入れます。たとえば、foos.py。基本クラスFooは通常、すべてのメソッド(およびインターフェースなど)を実装し、通常、ユーザーが直接作成することはありません。サブクラスは、ベースコンストラクターを上書きし、Fooの構築方法を指定する手段です。次に、次のように適切なコンストラクタを呼び出してFooを作成します。

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

静的メソッド、クラスメソッド、またはモジュールレベルの関数から構築されたファクトリーよりもエレガントで柔軟だと思います。

1
mdeff