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通りある可能性があるため、そのロジックをある程度分離したいと思います。意味がありますか?
静的メソッドはいつどのように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
クラスとは関係がないようです(または、少なくとも同じモジュール内の他の何よりも)。だから、それを無料の関数にしてください。
リンクされた質問への答えは具体的にこれを言います:
@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
はかなりあいまいなので。
あなたの最初の例は私にとって最も理にかなっています: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
...もあります)それは思い出させます古典の私:
私は常に、(a)システムの知識がなく、(b)システムの他の部分の知識を持っている人に、名前が何を示すかを考えようとします。常に成功しているとは限りません。
これが@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}