クラス本体内でクラスstaticmethodを呼び出していますか?


159

クラスの本体内から静的メソッドを使用しようとするとstaticmethod、次のように、組み込み関数をデコレーターとして使用して静的メソッドを定義します。

class Klass(object):

    @staticmethod  # use as decorator
    def _stat_func():
        return 42

    _ANS = _stat_func()  # call the staticmethod

    def method(self):
        ret = Klass._stat_func() + Klass._ANS
        return ret

次のエラーが発生します。

Traceback (most recent call last):<br>
  File "call_staticmethod.py", line 1, in <module>
    class Klass(object): 
  File "call_staticmethod.py", line 7, in Klass
    _ANS = _stat_func() 
  TypeError: 'staticmethod' object is not callable

私はこれがなぜ起こっているのか(記述子バインディング)を理解しており_stat_func()、最後に使用した後、次のように手動で静的メソッドに変換することで回避できます:

class Klass(object):

    def _stat_func():
        return 42

    _ANS = _stat_func()  # use the non-staticmethod version

    _stat_func = staticmethod(_stat_func)  # convert function to a static method

    def method(self):
        ret = Klass._stat_func() + Klass._ANS
        return ret

だから私の質問は:

これを達成するためのよりクリーンな、またはより「Pythonic」のような、より良い方法はありますか?


4
Pythonicityについて質問している場合、標準的なアドバイスはまったく使用staticmethodしないことです。通常、これらはモジュールレベルの関数としてより有用です。この場合、問題は問題ではありません。classmethod一方、...
ベンジャミンホジソン

1
@poorsod:はい、その代替案を知っています。ただし、この問題が発生した実際のコードでは、関数をモジュールレベルで配置するのではなく静的メソッドにすることは、質問で使用した単純な例よりも意味があります。
martineau

回答:


180

staticmethodオブジェクトは明らかに__func__元の生の関数を格納する属性を持っています(そうする必要があったのは理にかなっています)。だからこれはうまくいくでしょう:

class Klass(object):

    @staticmethod  # use as decorator
    def stat_func():
        return 42

    _ANS = stat_func.__func__()  # call the staticmethod

    def method(self):
        ret = Klass.stat_func()
        return ret

余談ですが、静的メソッドオブジェクトには、元の関数を格納するある種の属性があるのではないかと疑っていましたが、詳細はわかりませんでした。誰かに魚を与えるのではなく、魚を釣るように教えるという精神で、これは私が調査してそれを見つけるためにやったことです(私のPythonセッションのC&P):

>>> class Foo(object):
...     @staticmethod
...     def foo():
...         return 3
...     global z
...     z = foo

>>> z
<staticmethod object at 0x0000000002E40558>
>>> Foo.foo
<function foo at 0x0000000002E3CBA8>
>>> dir(z)
['__class__', '__delattr__', '__doc__', '__format__', '__func__', '__get__', '__getattribute__', '__hash__', '__init__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
>>> z.__func__
<function foo at 0x0000000002E3CBA8>

対話型セッションで同様の種類の掘り下げを行うと(dir非常に役立ちます)、このような種類の質問をすばやく解決できることがよくあります。


良い更新...私はドキュメントにそれが表示されていないので、あなたがこれをどのように知っているのかを尋ねようとしていました-これは「実装の詳細」かもしれないので、それを使用することに少し不安になります。
martineau

さらに読んだ後、それ__func__は単なる別名でim_funcあり、Python 3の前方互換性のためにPy 2.6に追加されました。
martineau 2012年

2
なるほど、技術的にはこの文脈では文書化されていません。
martineau 2012年

1
@AkshayHazari __func__静的メソッドの属性は、staticmethodデコレータを使用したことがない場合とまったく同じように、元の関数への参照を取得します。したがって、関数に引数が必要な場合は、を呼び出すときに引数を渡す必要があります__func__。エラーメッセージは、引数を指定していないように聞こえます。場合はstat_func、この記事の例では、二つの引数を取り、あなたが使用するであろう_ANS = stat_func.__func__(arg1, arg2)
ベン・

1
@AkshayHazariわかりません。それらは変数にすることができ、スコープ内変数でなければなりません:クラスが定義されているのと同じスコープ内で以前に定義されているか(多くの場合グローバル)、またはクラススコープ内で以前に定義されています(stat_funcそれ自体が変数です)。定義しているクラスのインスタンス属性を使用できないということですか?それは事実ですが、当然のことです。我々はしていない持って、我々はまだクラスを定義していることから、クラスのインスタンスを!しかし、とにかく、私はあなたが渡したいと思ったあらゆる議論の代役として彼らを単に意味しました。そこでリテラルを使用できます。
Ben

24

これは私が好む方法です:

class Klass(object):

    @staticmethod
    def stat_func():
        return 42

    _ANS = stat_func.__func__()

    def method(self):
        return self.__class__.stat_func() + self.__class__._ANS

DRYの原則のKlass.stat_funcため、私はこの解決策を好む。Python 3に新しい機能がある理由を思い出します:)super()

しかし、私は他の人に同意します。通常、最良の選択は、モジュールレベルの関数を定義することです。

たとえば、@staticmethod関数の場合、再帰はあまりよく見えない可能性があります(Klass.stat_func内部を呼び出すことでDRYの原則を破る必要がありますKlass.stat_func)。これは、self静的メソッド内への参照がないためです。モジュールレベルの機能を使用すると、すべてが正常に見えます。


self.__class__.stat_func()通常の方法で使用することにはを使用するよりも利点(DRYなど)があることに同意しますがKlass.stat_func()、それは実際には私の質問のトピックではありませんでした。
martineau 14

1
それ自体はDRY自体の問題ではありません。self.__class__サブクラスのオーバーライド場合ので良いですstat_funcし、Subclass.methodサブクラスの呼び出しますstat_func。しかし、正直に言うと、そのような状況では、静的メソッドではなく実際のメソッドを使用する方がはるかに優れています。
asmeurer 2017

@asmeurer:クラスのインスタンスがまだ作成されていないため、実際のメソッドを使用できません。クラス自体がまだ完全に定義されていないため、実際のメソッドは使用できません。
martineau

私のクラスの静的メソッド(継承されるため、親は実際に関数を持っています)を呼び出す方法が必要でしたが、これは断然最善のようです(後でオーバーライドしても引き続き機能します)。
whitey04 2018年

11

クラス定義の後にクラス属性を挿入するのはどうですか?

class Klass(object):

    @staticmethod  # use as decorator
    def stat_func():
        return 42

    def method(self):
        ret = Klass.stat_func()
        return ret

Klass._ANS = Klass.stat_func()  # inject the class attribute with static method value

1
これは回避策での最初の試みに似ていますが、関係する属性に先行アンダースコアがあり、プライベートであるため、クラス内にあるものを好みます。
martineau

11

これは、静的メソッドが記述子であるためであり、記述子プロトコルを実行して真の呼び出し可能オブジェクトを取得するには、クラスレベルの属性フェッチが必要です。

ソースコードから:

クラス(例C.f())またはインスタンス(例C().f())で呼び出すことができます。クラスを除いて、インスタンスは無視されます。

しかし、クラスが定義されている間、クラスの内部から直接ではありません。

しかし、1人のコメンターが述べたように、これは実際には「Pythonic」デザインではありません。代わりにモジュールレベルの関数を使用してください。


質問で述べたように、元のコードが機能しなかった理由を理解しています。なぜそれが非Pythonicと見なされるのか説明できますか(またはそうするものへのリンクを提供できます)?
martineau

staticmethodが正しく動作するには、クラスオブジェクト自体が必要です。ただし、クラスレベルでクラス内から呼び出す場合、その時点ではクラスは完全には定義されていません。そのため、まだ参照することはできません。静的メソッドを定義した後、クラスの外部から呼び出してもかまいません。しかし、「Pythonic」は実際には美学のように明確に定義されていません。そのコードに対する私自身の第一印象は好意的ではなかったと言えます。
キース

どのバージョン(または両方)の印象ですか?そして、なぜ、具体的には?
martineau 2012年

私はあなたの最終的な目標が何であるかを推測することしかできませんが、動的なクラスレベルの属性が必要なようです。これはメタクラスの仕事のように見えます。しかし、この場合も、機能を犠牲にすることなく、デザインを単純化して排除または簡素化する別の方法があるかもしれません。
キース、

3
いいえ、私がしたいことは実際には非常に簡単です-これは、いくつかの一般的なコードを除外して再利用することです。クラスの作成時にプライベートクラス属性を作成し、後で1つ以上のクラスメソッド内で作成します。この一般的なコードはクラスの外では使用されないので、当然、その一部にしたいと思います。メタクラス(またはクラスデコレータ)を使用しても機能しますが、これは簡単に実行できるはずの何か、IMHOにとってはやり過ぎのようです。
martineau 2012年

8

このソリューションはどうですか?@staticmethodデコレータ実装の知識に依存しません。内部クラスStaticMethodは、静的初期化関数のコンテナーとして機能します。

class Klass(object):

    class StaticMethod:
        @staticmethod  # use as decorator
        def _stat_func():
            return 42

    _ANS = StaticMethod._stat_func()  # call the staticmethod

    def method(self):
        ret = self.StaticMethod._stat_func() + Klass._ANS
        return ret

2
創造性のために+1しまし__func__たが、公式に文書化されているので、もう使用する必要はありません(「Python 2.7の新機能のその他の言語の変更」と「問題5982への参照」を参照)。おそらく2.6より前のPythonバージョンでも機能するため(__func__最初にの同義語として導入されたとき)、このソリューションはさらに移植性が高くなりim_funcます。
martineau 2014年

これは、Python 2.6で動作する唯一のソリューションです。
ベンセルメ2017

@benselme:Python 2.6がインストールされていないため、申し立てを確認することはできませんが、それだけが唯一の疑いです...
martineau
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.