web-dev-qa-db-ja.com

モジュール関数vs staticmethod vs classmethod vsデコレータなし:どのイディオムがよりpythonicですか?

私はJava Pythonオンとオフをいじくり回している開発者です。最近、つまずきました この記事 よくある間違いに言及しています。 JavaプログラマーがPythonを拾うと、プログラマーが作成します。

Javaの静的メソッドは、Pythonクラスメソッドに変換されません。確かに、ほぼ同じ効果になりますが、クラスメソッドの目標はJava(非デフォルトのコンストラクターを継承するなど)では通常不可能なことを実行することです。Java staticメソッドclassmethodやstaticmethodではなく、モジュールレベルの関数です(さらに、静的な最終フィールドはモジュールレベルの定数に変換する必要があります)。

これはパフォーマンスの問題ではありませんが、PythonこのようなJavaイディオムコードを使用しなければならないプログラマーは、Foo.Foo.someMethodと入力するだけでかなりイライラしますFoo.someFunction。ただし、クラスメソッドの呼び出しには、静的メソッドまたは関数の呼び出しにはない追加のメモリ割り当てが含まれることに注意してください。

ああ、すべてのFoo.Bar.Baz属性チェーンも無料ではありません。 Javaでは、これらのドット表記の名前はコンパイラーによって検索されるため、実行時にそれらがいくつあるかは実際には関係ありません。 Pythonでは、実行時にルックアップが発生するため、各ドットがカウントされます。 (Pythonでは、「フラットはネストよりも優れている」ことを思い出してください。ただし、パフォーマンスに関することよりも、「読みやすさのカウント」と「シンプルは複雑よりも優れている」に関連しています。)

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:

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

classmethod:

  • Staticmethodと同じですが、クラスを最初の引数として渡します。

通常の方法(Python 3のみ)

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

私はこれを考え直していますか?これは問題ではありませんか?助けてください!

64
Doval

それについて考える最も簡単な方法は、メソッドがその作業を行うために必要なオブジェクトのタイプの観点から考えることです。メソッドがインスタンスへのアクセスを必要とする場合、それを通常のメソッドにします。クラスへのアクセスが必要な場合は、クラスメソッドにします。クラスまたはインスタンスにアクセスする必要がない場合は、関数にします。何かを静的メソッドにする必要はめったにありませんが、クラスにアクセスする必要がない場合でも、関数をクラスで「グループ化」する必要がある場合(たとえば、オーバーライドできるようにする場合)、あなたはそれをstaticmethodにすることができます。

モジュールレベルで関数を配置しても、名前空間が「汚染されない」と付け加えます。関数が使用されることを意図している場合、それらは名前空間を汚染せず、使用されるべきようにそれを使用しています。関数は、クラスや他の何かと同様に、モジュール内の正当なオブジェクトです。存在する理由がない場合、クラス内の関数を非表示にする理由はありません。

82
BrenBarn

BrenBarn による素晴らしい答えですが、私はを変更します 'クラスまたはインスタンスにアクセスする必要がなければ、それを関数にします'

'クラスまたはインスタンスへのアクセスを必要としない場合...isテーマに関連するクラス(典型的な例:ヘルパー関数と変換他のクラスメソッドで使用される関数または代替コンストラクターで使用される関数)を使用し、staticmethod

それ以外の場合は、モジュール関数にします

28
smci

これは実際には答えではなく、長いコメントです。

さらに不可解なのは、このコード:

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

Python 2.7.3で期待どおりに失敗しますが、3.2.3では正常に動作します(ただし、クラスでのみ、Aのインスタンスでメソッドを呼び出すことはできません。)

ここで何が起こるか説明しようと思います。

これは、厳密に言えば、「通常の」インスタンスメソッドプロトコルの悪用です。

ここで定義するのはメソッドですが、最初の(そして唯一の)パラメーターはselfではなくxという名前です。もちろん、Aのインスタンスでメソッドを呼び出すことができますが、次のように呼び出す必要があります。

A().foo()

または

a = A()
a.foo()

そのため、インスタンスは最初の引数として関数に与えられます。

クラスを介して通常のメソッドを呼び出す可能性は常に存在し、

a = A()
A.foo(a)

ここで、インスタンスではなくクラスのメソッドを呼び出すと、最初のパラメーターは自動で指定されませんが、それを提供する必要があります。

これがAのインスタンスである限り、すべてが問題ありません。それを別のものにすると、IMOはプロトコルの不正使用となり、Py2とPy3の違いになります。

Py2では、A.fooはバインドされていないメソッドに変換されるため、その最初の引数は「生きる」クラスのインスタンスである必要があります。他のメソッドで呼び出すと失敗します。

Py3では、このチェックは廃止され、A.fooは元の関数オブジェクトです。したがって、すべてを最初の引数として呼び出すことができますが、私はそうしません。メソッドの最初のパラメーターは常にselfという名前で、selfのセマンティクスを持つ必要があります。

15
glglgl

最良の答えは、関数がどのように使用されるかによって異なります。私の場合、Jupyterノートブックで使用されるアプリケーションパッケージを作成します。私の主な目標は、ユーザーにとって物事を簡単にすることです。

関数定義の主な利点は、ユーザーが「as」キーワードを使用して定義ファイルをインポートできることです。これにより、ユーザーはnumpyまたはmatplotlibで関数を呼び出すのと同じ方法で関数を呼び出すことができます。

Pythonの欠点の1つは、それ以上の割り当てから名前を保護できないことです。ただし、ノートブックの上部に「numpy as npをインポート」が表示される場合、「np」共通の変数名として使用すべきではありませんが、明らかにクラス名でも同じことを実現できますが、ユーザーの知識は非常に重要です。

ただし、パッケージ内では、静的メソッドを使用することを好みます。私のソフトウェアアーキテクチャはオブジェクト指向であり、複数のターゲット言語に使用するEclipseを使用して記述しています。ソースファイルを開いて、トップレベルのクラス定義、1レベルインデントされたメソッド定義などを表示すると便利です。このレベルのコードの対象読者は主に他のアナリストと開発者です。そのため、言語固有のイディオムは避けた方が良いでしょう。

Pythonネームスペース管理、特にオブジェクトが自身に参照を渡すデザインパターンを使用する場合、呼び出されたオブジェクトが定義されたメソッドを呼び出すことができる場合、だから私は多くの完全修飾名と明示的なインスタンス変数(selfを使用)を使用しますが、他の言語ではインタープリターやコンパイラーがクラスと静的メソッドを使用してこれを行う方が簡単であるため、抽象化と情報隠蔽が最も役立つ複雑なパッケージに適していると思います。

0
user5920660