27

これは、Pythonを使用して同じデータのさまざまな形式からクラスまたはタイプのインスタンスを作成するためのベストプラクティスに関する質問です。クラスメソッドを使用する方が良いですか、それとも別の関数を一緒に使用する方が良いですか?ドキュメントのサイズを説明するために使用されるクラスがあるとしましょう。(注:これは単なる例です。ドキュメントのサイズを説明する最良の方法ではなく、クラスのインスタンスを作成するための最良の方法を知りたいです。)

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)

また

def create_instance_from_bytes(bytes):
    bits = bytes * Size.BYTE
    return Size(bits)

これは問題のようには思えないかもしれませんし、おそらく両方の例が有効ですが、私はこのようなものを実装する必要があるたびにそれについて考えます。クラスとファクトリメソッドを結び付けることの組織的な利点が好きなので、長い間、クラスメソッドアプローチを好みました。また、クラスメソッドを使用すると、サブクラスのインスタンスを作成する機能が保持されるため、オブジェクト指向になります。一方、友人が「疑わしいときは標準ライブラリと同じことをしてください」と言ったことがありますが、私はまだ標準ライブラリでその例を見つけていません。

4

1 に答える 1

33

まず、ほとんどの場合、このようなものが必要だと思いますが、必要ありません。これは、Python を Java のように扱おうとしている兆候です。解決策は、一歩下がって、なぜファクトリが必要なのかを尋ねることです。

多くの場合、最も簡単なことは、コンストラクターにデフォルト/オプション/キーワード引数を指定することです。Java では絶対にそのように記述しない場合でも (C++ や ObjC ではオーバーロードされたコンストラクターが正しくないと感じる場合でも)、Python では完全に自然に見える場合があります。たとえばsize = Size(bytes=20)、またはsize = Size(20, Size.BYTES)妥当に見えます。Bytes(20)さらに言えば、オーバーロードだけを継承して何も追加しないクラスはSize合理__init__的に見えます。そして、これらを定義するのは簡単です:

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

ただし、ファクトリ関数が必要な場合もあります。それで、あなたは何をしますか?さて、「工場」にひとくくりにされることが多いのは、2 種類のものです。

A@classmethodは「代替コンストラクター」を実行するための慣用的な方法です。stdlib 全体に例があります — <code>itertools.chain.from_iterabledatetime.datetime.fromordinalなど。

関数は、「実際のクラスが何であるかは気にしない」ファクトリーを行うための慣用的な方法です。たとえば、組み込みopen関数を見てください。3.3 で何を返すか知っていますか? 手入れする?いいえ。それが関数である理由io.TextIOWrapper.openです。

あなたの与えられた例は完全に正当なユースケースのように見え、「代替コンストラクター」ビンにかなり明確に適合します(「追加の引数を持つコンストラクター」ビンに適合しない場合)。

于 2013-02-21T00:28:16.487 に答える