1. ホーム
  2. python

[解決済み] モジュール関数 vs staticmethod vs classmethod vs デコレータなし。どのイディオムがよりパイソン的か?

2023-05-17 02:11:05

質問

私はPythonを時々使っているJava開発者です。私は最近、偶然 この記事 この記事では、JavaプログラマがPythonを始めるときに犯しがちな失敗について言及しています。最初のものが私の目を引きました。

Javaでの静的メソッドはPythonのクラスメソッドに変換されません。確かに、それは多かれ少なかれ同じ効果をもたらしますが、クラスメソッドの目的は、実際にはJavaでは通常不可能なこと(デフォルトでないコンストラクタを継承するような)を行うことなのです。Java の静的メソッドの慣用的な翻訳は、通常、モジュールレベルの関数で、classmethod や staticmethod ではありません。(そして、静的な最終フィールドはモジュールレベルの定数に翻訳されるべきです。)

これはパフォーマンスの問題ではありませんが、このようなJavaの慣用的なコードで作業しなければならないPythonプログラマは、単にFoo.someFunctionとすべき時にFoo.Foo.someMethodとタイプすることでむしろいらいらすることでしょう。しかし、クラスメソッドの呼び出しは、staticmethodや関数の呼び出しにはない、追加のメモリ割り当てを含むことに注意してください。

それと、Foo.Bar.Bazの属性チェーンもタダではありません。Javaでは、これらのドット付きの名前はコンパイラによって調べられるので、実行時にいくつあってもかまいません。Pythonでは、ルックアップは実行時に行われるので、各ドットはカウントされます。(Pythonでは、quot;Flat is better than nested"は、パフォーマンスについてというよりも、quot;Readability counts" や "Simple is better than complex"に関連していることを思い出してください).

私はこれが少し奇妙であると感じました。 staticmethod にはこう書かれています。

Pythonの静的メソッドは、JavaやC++で見られるものと似ています。また、代替のクラスコンストラクタを作成するのに便利な変形として classmethod() を参照してください。

さらに不可解なのは、このコードです。

class A:
    def foo(x):
        print(x)
A.foo(5)

Python 2.7.3 では予想通り失敗しますが、3.2.3 では正常に動作します (ただし、Aのインスタンスに対してメソッドを呼び出すことはできず、クラスに対してのみ呼び出すことができます)。

つまり、静的メソッドを実装する方法は3つあり(classmethodの使用を含めると4つ)、それぞれに微妙な違いがあり、そのうちの1つは文書化されていないようです。これは、Pythonのマントラである 1 つの、そしてできればたった 1 つの -- それを行うための明白な方法があるべきです。 どのイディオムが最もPythonicなのでしょうか?それぞれの長所と短所は何ですか?

今のところ理解しているのはこんなところです。

モジュール機能。

  • Foo.Foo.f()問題の回避
  • 他の方法よりもモジュールの名前空間を汚染します。
  • 継承がない

staticmethodです。

  • クラスに関連する関数をクラス内部で保持し、モジュールの名前空間から除外します。
  • クラスのインスタンス上で関数を呼び出せるようにする。
  • サブクラスはメソッドをオーバーライドすることができます。

クラスメソッドです。

  • staticmethodと同じですが、第一引数としてクラスを渡します。

通常のメソッド (Python 3のみ) :

  • staticmethodと同じですが、クラスのインスタンスに対してメソッドを呼び出すことはできません。

私は考えすぎでしょうか?これは問題ないのでしょうか?どうか助けてください。

どのように解決するのですか?

最も簡単な方法は、メソッドが仕事をするためにどのような種類のオブジェクトを必要とするかという観点から考えることです。 メソッドがインスタンスにアクセスする必要がある場合は、通常のメソッドにします。 クラスへのアクセスが必要な場合は、クラスメソッドとします。 クラスやインスタンスにアクセスする必要がない場合は、関数にします。 staticmethodにする必要はほとんどありませんが、クラスへのアクセスが必要ないにもかかわらず、関数をクラスと一緒に(たとえばオーバーライドできるように)グループ化したい場合は、staticmethodにすることができるかもしれませんね。

モジュールレベルに関数を置くことは、名前空間を汚染しないことを付け加えます。 もし関数が使用されることを意図しているならば、名前空間を汚染しているのではなく、使用されるべきものとしてそれを使用しているのです。 関数は、クラスや他のものと同じように、モジュールの中の正当なオブジェクトです。 もし、そこにあるべき理由がないのなら、関数をクラスの中に隠す理由はないのです。