web-dev-qa-db-ja.com

python-ベストプラクティスで静的メソッドを使用する

静的メソッドはいつどのようにPythonで使用されると想定されていますか?オブジェクトのインスタンスを作成するためのファクトリメソッドとしてクラスメソッドを使用することはすでに確立されていますが、可能な場合は避ける必要があります。言い換えると、クラスメソッドを代替コンストラクターとして使用することはベストプラクティスではありません( pythonオブジェクトのファクトリメソッド-ベストプラクティス を参照)。

データベース内のエンティティデータを表すために使用されるクラスがあるとしましょう。データがフィールド名とフィールド値を含むdictオブジェクトであり、フィールドの1つがデータを一意にするID番号であるとします。

_class Entity(object):
    def __init__(self, data, db_connection):
        self._data = data
        self._db_connection
_

ここで、私の___init___メソッドはエンティティデータdictオブジェクトを取得します。 ID番号しかなく、Entityインスタンスを作成したいとします。まず、残りのデータを見つけてから、Entityオブジェクトのインスタンスを作成する必要があります。以前の質問から、ファクトリメソッドとしてクラスメソッドを使用することは、可能な場合はおそらく避けるべきであると判断しました。

_class Entity(object):

    @classmethod
    def from_id(cls, id_number, db_connection):
        filters = [['id', 'is', id_number]]
        data = db_connection.find(filters)
        return cls(data, db_connection)

    def __init__(self, data, db_connection):
        self._data = data
        self._db_connection


# Create entity
entity = Entity.from_id(id_number, db_connection)
_

上記は、すべきでないこと、または少なくとも代替手段がある場合にすべきでないことの例です。クラスメソッドを編集して、ユーティリティメソッドになり、ファクトリメソッドを少なくすることが有効なソリューションになるかどうか疑問に思っています。言い換えると、次の例は静的メソッドを使用するためのベストプラクティスに準拠していますか?.

_class Entity(object):

    @staticmethod
    def data_from_id(id_number, db_connection):
        filters = [['id', 'is', id_number]]
        data = db_connection.find(filters)
        return data


# Create entity
data = Entity.data_from_id(id_number, db_connection)
entity = Entity(data)
_

または、スタンドアロン関数を使用してID番号からエンティティデータを検索する方が理にかなっていますか。

_def find_data_from_id(id_number, db_connection):
    filters = [['id', 'is', id_number]]
    data = db_connection.find(filters)
    return data


# Create entity.
data = find_data_from_id(id_number, db_connection)
entity = Entity(data, db_connection)
_

注:___init___メソッドを変更したくありません。以前、私の___init___メソッドをこの__init__(self, data=None, id_number=None)のように見せることを提案されていましたが、エンティティデータを見つける方法は101通りある可能性があるため、そのロジックをある程度分離したいと思います。意味がありますか?

14
Yani

静的メソッドはいつどのようにPythonで使用されると想定されていますか?

Glibの答えは次のとおりです。あまり頻繁ではありません。

グリバーでさえ、役に立たない答えは次のとおりです。コードが読みやすくなるとき。


まず、 ドキュメント に迂回しましょう:

Pythonの静的メソッドは、JavaまたはC++で見られるものと似ています。代替クラスコンストラクターの作成に役立つバリアントについては、classmethod()も参照してください。

したがって、C++で静的メソッドが必要な場合は、Pythonで静的メソッドが必要です。

うーん、ダメ。

Javaには関数はなく、メソッドだけが存在するため、静的メソッドのバンドルにすぎない疑似クラスを作成することになります。 Pythonで同じことを行う方法は、無料の関数を使用することです。

それはかなり明白です。ただし、適切なクラスが関数をくさびで留めるためにできるだけ一生懸命に見えるのは良いJavaスタイルなので、同じことをするのは悪いですが、それらの疑似クラスを書くことを避けることができますPython style —繰り返しますが、無料の関数を使用します—これはそれほど明白ではありません。

C++にはJavaと同じ制限はありませんが、とにかく多くのC++スタイルはかなり似ています。 (一方、「フリー関数はクラスのインターフェイスの一部である」というイディオムを内部化した「Modern C++」プログラマーの場合、「静的メソッドはどこで役立つか」という本能はおそらくPythonにとってかなり適切です。)


しかし、別の言語からではなく、第一原理からこれに到達している場合は、物事を見るより簡単な方法があります。

_@staticmethod_は基本的に単なるグローバル関数です。関数foo_module.bar()があり、それがfoo_module.BazClass.bar()と綴られていると、何らかの理由で読みやすくなる場合は、それを_@staticmethod_にします。そうでない場合は、しないでください。それが本当にすべてです。唯一の問題は、慣用的なPythonプログラマーにとってより読みやすいものに対する本能を構築することです。

そしてもちろん、インスタンスではなくクラスへのアクセスが必要な場合は_@classmethod_を使用します。ドキュメントが示すように、代替コンストラクターがそのパラダイムケースです。多くの場合canは、クラスを明示的に参照するだけで、_@classmethod_を_@staticmethod_でシミュレートできます(特に、クラスがない場合)多くのサブクラス化)、すべきではありません。


最後に、あなたの特定の質問に行きます:

クライアントがIDでデータを検索する必要がある唯一の理由が、Entityを構築することである場合、それは公開してはならない実装の詳細のように聞こえ、クライアントコードをより複雑にします。コンストラクターを使用するだけです。 ___init___を変更したくない場合(そして、変更したくない正当な理由がある場合)、代替コンストラクターとして_@classmethod_を使用します。Entity.from_id(id_number, db_connection)

一方、そのルックアップがEntity構造とは関係のない他の場合にクライアントにとって本質的に役立つものである場合、これはEntityクラスとは関係がないようです(または、少なくとも同じモジュール内の他の何よりも)。だから、それを無料の関数にしてください。

24
abarnert

リンクされた質問への答えは具体的にこれを言います:

@classmethodは、「代替コンストラクター」を実行する慣用的な方法です。stdlib全体に例があります。itertools.chain.from_iterable、datetime.datetime.fromordinalなどです。

ですから、クラスメソッドの使用は本質的に悪いという考えをどのようにして得たのかわかりません。コードに従い、APIを簡単に使用できるため、特定の状況でクラスメソッドを使用するというアイデアが実際に気に入っています。

別の方法は、次のようなデフォルトのコンストラクター引数を使用することです。

class Entity(object):
    def __init__(self, id, db_connection, data=None):
        self.id = id
        self.db_connection = db_connection
        if data is None:
            self.data = self.from_id(id, db_connection)
        else:
            self.data = data

    def from_id(cls, id_number, db_connection):
        filters = [['id', 'is', id_number]]
        return db_connection.find(filters)

ただし、最初に作成したclassmethodバージョンの方が好きです。特にdataはかなりあいまいなので。

11
Josh Smeaton

あなたの最初の例は私にとって最も理にかなっています:Entity.from_idはかなり簡潔で明確です。

次の2つの例では、何が返されるかを説明していないdataの使用を回避します。 dataは、Entityを構築するために使用されます。 dataの作成に使用されているEntityについて具体的に知りたい場合は、メソッドにEntity.with_data_for_idまたは同等の関数entity_with_data_for_idのような名前を付けることができます。

findなどの動詞を使用すると、戻り値が表示されないため、かなり混乱する可能性があります。データが見つかったときに関数は何をする必要がありますか? (はい、strにはfindメソッドがあることに気付きました。index_ofという名前の方がいいのではないでしょうか?しかし、index...もあります)それは思い出させます古典の私:

find x

私は常に、(a)システムの知識がなく、(b)システムの他の部分の知識を持っている人に、名前が何を示すかを考えようとします。常に成功しているとは限りません。

7
Nicholas Riley

これが@staticmethodの適切なユースケースです。

私はサイドプロジェクトとしてゲームに取り組んでいます。そのゲームの一部には、統計に基づいたサイコロの転がり、キャラクターの統計に影響を与えるアイテムや効果を拾う可能性が含まれます(良くも悪くも)。

ゲームでサイコロを振るときは、基本的に言う必要があります...基本キャラクターの統計を取得し、インベントリと効果の統計をこのグランドネットの数字に追加します。

プログラムに方法を指示せずに、これらの抽象オブジェクトを取得して追加することはできません。クラスレベルでもインスタンスレベルでも何もしていません。一部のグローバルモジュールで関数を定義したくありませんでした。最後の最善のオプションは、統計を合計するための静的メソッドを使用することでした。この方法が最も理にかなっています。

class Stats:

    attribs = ['strength', 'speed', 'intellect', 'tenacity']

    def __init__(self,
                 strength=0,
                 speed=0,
                 intellect=0,
                 tenacity=0
                 ):
        self.strength = int(strength)
        self.speed = int(speed)
        self.intellect = int(intellect)
        self.tenacity = int(tenacity)

    # combine adds stats objects together and returns a single stats object
    @staticmethod
    def combine(*args: 'Stats'):
        assert all(isinstance(arg, Stats) for arg in args)
        return_stats = Stats()
        for stat in Stats.attribs:
            for _ in args:
                setattr(return_stats, stat,
                        getattr(return_stats, stat) + getattr(_, stat))
        return (return_stats)

これにより、統計の組み合わせの呼び出しが次のように機能します

a = Stats(strength=3, intellect=3)
b = Stats(strength=1, intellect=-1)
c = Stats(tenacity=5)
print(Stats.combine(a, b, c).__dict__)

{'強さ':4、 '速度':0、 '知性':2、 '粘り強さ':5}

0
Patrick Rutz