これは、Pythonを使用して同じデータの異なる形式からクラスまたはタイプのインスタンスを作成するためのベストプラクティスに関する質問です。クラスメソッドを使用する方が良いですか、それとも別個の関数を完全に使用する方が良いですか?ドキュメントのサイズを説明するために使用されるクラスがあるとします。 (注:これは単なる例です。クラスのインスタンスを作成する最良の方法notがドキュメントのサイズを説明する最良の方法を知りたいです。)
class Size(object):
"""
Utility object used to describe the size of a document.
"""
BYTE = 8
KILO = 1024
def __init__(self, bits):
self._bits = bits
@property
def bits(self):
return float(self._bits)
@property
def bytes(self):
return self.bits / self.BYTE
@property
def kilobits(self):
return self.bits / self.KILO
@property
def kilobytes(self):
return self.bytes / self.KILO
@property
def megabits(self):
return self.kilobits / self.KILO
@property
def megabytes(self):
return self.kilobytes / self.KILO
ぼくの __init__
メソッドは、ビット(ビットおよびビットのみであり、そのように保持したい)で表されるサイズ値を受け取りますが、サイズ値がバイトであり、クラスのインスタンスを作成するとします。クラスメソッドを使用する方が良いですか、それとも別個の関数を完全に使用する方が良いですか?
class Size(object):
"""
Utility object used to describe the size of a document.
"""
BYTE = 8
KILO = 1024
@classmethod
def from_bytes(cls, bytes):
bits = bytes * cls.BYTE
return cls(bits)
OR
def create_instance_from_bytes(bytes):
bits = bytes * Size.BYTE
return Size(bits)
これは問題のように見えないかもしれませんし、おそらく両方の例が有効ですが、私はこのようなものを実装する必要があるたびにそれについて考えます。長い間、クラスとファクトリメソッドを結びつけることの組織的な利点が好きなので、クラスメソッドアプローチを好んでいました。また、クラスメソッドを使用すると、サブクラスのインスタンスを作成する機能が保持されるため、よりオブジェクト指向になります。一方、「疑わしいときは標準ライブラリの機能を実行する」と友人がかつて言ったことがありますが、標準ライブラリでこれを示す例はまだありません。
まず、ほとんどの場合、このようなものが必要だと思いますが、必要はありません。これは、JavaのようにPython=を処理しようとしていることを示しています。解決策は、一歩下がって、なぜファクトリが必要なのかを尋ねることです。
多くの場合、最も簡単なことは、defaulted/optional/keyword引数を持つコンストラクターを用意することです。 Javaでそのように記述したことがない場合でも、オーバーロードされたコンストラクターがC++またはObjCで間違っていると感じる場合でも、Pythonでは完全に自然に見えるかもしれません。たとえば、size = Size(bytes=20)
、またはsize = Size(20, Size.BYTES)
は適切に見えます。さらに言えば、Size
を継承し、___init__
_オーバーロード以外は何も追加しないBytes(20)
クラスは合理的です。そして、これらを定義するのは簡単です:
_def __init__(self, *, bits=None, bytes=None, kilobits=None, kilobytes=None):
_
または:
_BITS, BYTES, KILOBITS, KILOBYTES = 1, 8, 1024, 8192 # or object(), object(), object(), object()
def __init__(self, count, unit=Size.BITS):
_
しかし、時にはdoファクトリ関数が必要です。それで、あなたはその後何をしますか?よく、2つの種類のものが一緒に「工場」にまとめられることがよくあります。
_@classmethod
_は、「代替コンストラクタ」を実行する慣用的な方法です。stdlibには、_itertools.chain.from_iterable
_、_datetime.datetime.fromordinal
_などの例があります。
関数は、「実際のクラスが何であるかは気にしない」ファクトリーを実行する慣用的な方法です。たとえば、組み込みのopen
関数を見てください。 3.3で何が返されるか知っていますか?手入れする?いいえ。それが_io.TextIOWrapper.open
_などではなく関数である理由です。
あなたの与えられた例は完全に正当なユースケースのようであり、「代替コンストラクター」ビンにかなり明確に適合します(「追加の引数を持つコンストラクター」ビンに適合しない場合)。