web-dev-qa-db-ja.com

関数、非バインドメソッド、バインドメソッドの違いは何ですか?

this answer のコメントスレッドに関する議論のため、この質問をしています。私は90%の方法で頭を動かしています。

In [1]: class A(object):  # class named 'A'
   ...:     def f1(self): pass
   ...:
In [2]: a = A()  # an instance

f1は3つの異なる形式で存在します。

In [3]: a.f1  # a bound method
Out[3]: <bound method a.f1 of <__main__.A object at 0x039BE870>>
In [4]: A.f1  # an unbound method
Out[4]: <unbound method A.f1>
In [5]: a.__dict__['f1']  # doesn't exist
KeyError: 'f1'
In [6]: A.__dict__['f1']  # a function
Out[6]: <function __main__.f1>

boundメソッドunboundメソッドfunctionオブジェクト、それらはすべてf1?これら3つのオブジェクトをどのように呼び出すのですか?それらはどのように互いに変換できますか? documentation このことについては理解するのが非常に難しいです。

56

関数は、defステートメントまたはlambdaによって作成されます。 Python 2)の下で、関数がclassステートメントの本体内に表示される(またはtypeクラス構築呼び出しに渡される)と、 非バインドメソッド。(Python 3には非バインドメソッドがありません。以下を参照してください。)クラスインスタンスで関数にアクセスすると、バインドメソッドに変換されます。インスタンスに最初のselfパラメーターとしてインスタンスを自動的に提供します。

_def f1(self):
    pass
_

ここで_f1_は関数です。

_class C(object):
    f1 = f1
_

現在、_C.f1_はバインドされていないメソッドです。

_>>> C.f1
<unbound method C.f1>
>>> C.f1.im_func is f1
True
_

typeクラスコンストラクターも使用できます。

_>>> C2 = type('C2', (object,), {'f1': f1})
>>> C2.f1
<unbound method C2.f1>
_

_f1_を非バインドメソッドに手動で変換できます。

_>>> import types
>>> types.MethodType(f1, None, C)
<unbound method C.f1>
_

非バインドメソッドは、クラスインスタンスのアクセスによってバインドされます。

_>>> C().f1
<bound method C.f1 of <__main__.C object at 0x2abeecf87250>>
_

アクセスは、記述子プロトコルを介した呼び出しに変換されます。

_>>> C.f1.__get__(C(), C)
<bound method C.f1 of <__main__.C object at 0x2abeecf871d0>>
_

これらを組み合わせる:

_>>> types.MethodType(f1, None, C).__get__(C(), C)
<bound method C.f1 of <__main__.C object at 0x2abeecf87310>>
_

または直接:

_>>> types.MethodType(f1, C(), C)                
<bound method C.f1 of <__main__.C object at 0x2abeecf871d0>>
_

関数と非バインドメソッドの主な違いは、メソッドがバインドされているクラスを知っていることです。非バインドメソッドの呼び出しまたはバインドには、そのクラスタイプのインスタンスが必要です。

_>>> f1(None)
>>> C.f1(None)
TypeError: unbound method f1() must be called with C instance as first argument (got NoneType instance instead)
>>> class D(object): pass
>>> f1.__get__(D(), D)
<bound method D.f1 of <__main__.D object at 0x7f6c98cfe290>>
>>> C.f1.__get__(D(), D)
<unbound method C.f1>
_

関数と非バインドメソッドの違いはごくわずかであるため、Python 3は区別を取り除きます; Python 3はクラスインスタンスの関数にアクセスします関数自体を提供します:

_>>> C.f1
<function f1 at 0x7fdd06c4cd40>
>>> C.f1 is f1
True
_

Python 2とPython 3の場合、これら3つは同等です:

_f1(C())
C.f1(C())
C().f1()
_

関数をインスタンスにバインドすると、その最初のパラメーター(従来はselfと呼ばれる)がインスタンスに固定されます。したがって、バインドメソッドC().f1は次のいずれかと同等です。

_(lamdba *args, **kwargs: f1(C(), *args, **kwargs))
functools.partial(f1, C())
_
64
ecatmur

理解しにくい

まあ、それは非常に難しいトピックであり、記述子に関係しています。

関数から始めましょう。ここではすべてが明確です-あなたはそれを呼び出すだけで、実行中に提供されたすべての引数が渡されます:

_>>> f = A.__dict__['f1']
>>> f(1)
1
_

通常のTypeErrorは、パラメーターの数に問題がある場合に発生します。

_>>> f()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: f1() takes exactly 1 argument (0 given)
_

さて、メソッド。メソッドは、少しスパイスの効いた関数です。ここで記述子がゲームに登場します。 データモデル で説明されているように、_A.f1_およびA().f1はそれぞれA.__dict__['f1'].__get__(None, A)およびtype(a).__dict__['f1'].__get__(a, type(a))に変換されます。そして、これらの___get___の結果は、生の_f1_関数とは異なります。これらのオブジェクトは、元の_f1_のラッパーであり、いくつかの追加ロジックが含まれています。

_unbound method_の場合、このロジックには、最初の引数がAのインスタンスであるかどうかのチェックが含まれます。

_>>> f = A.f1
>>> f()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unbound method f1() must be called with A instance as first argument (got nothing instead)
>>> f(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unbound method f1() must be called with A instance as first argument (got int instance instead) 
_

このチェックが成功すると、そのインスタンスを最初の引数として元の_f1_を実行します:

_>>> f(A())
<__main__.A object at 0x800f238d0>
_

_im_self_属性はNoneであることに注意してください:

_>>> f.im_self is None
True
_

_bound method_の場合、このロジックはすぐに元の_f1_に作成されたAのインスタンスを提供します(このインスタンスは実際には_im_self_属性に格納されます)。

_>>> f = A().f1
>>> f.im_self
<__main__.A object at 0x800f23950>
>>> f()
<__main__.A object at 0x800f23950>
_

したがって、boundは、基になる関数が特定のインスタンスにバインドされていることを意味します。 unboundは、クラスにのみバインドされていることを意味します。

8

関数オブジェクトは、関数定義によって作成された呼び出し可能なオブジェクトです。バインドされたメソッドとバインドされていないメソッドはどちらも、ドットバイナリ演算子によって呼び出された記述子によって作成された呼び出し可能なオブジェクトです。

バインドおよび非バインドメソッドオブジェクトには、3つの主要なプロパティがあります。im_funcはクラスで定義された関数オブジェクト、im_classはクラスであり、im_selfはクラスインスタンスです。非バインドメソッドの場合、im_selfNoneです。

バウンドメソッドが呼び出されると、im_func with im_selfは、最初のパラメーターとして呼び出しパラメーターの後に続きました。バインドされていないメソッドは、呼び出しパラメーターだけで基になる関数を呼び出します。

3

今日私が見た興味深いことの1つは、関数をクラスメンバーに割り当てると、そのメソッドが非バインドメソッドになることです。といった:

class Test(object):
    @classmethod
    def initialize_class(cls):
        def print_string(self, str):
            print(str)
        # Here if I do print(print_string), I see a function
        cls.print_proc = print_string
        # Here if I do print(cls.print_proc), I see an unbound method; so if I
        # get a Test object o, I can call o.print_proc("Hello")
2
robbie fan

詳細については、 Python 2 および Python のドキュメントを参照してください。

私の解釈は次のとおりです。

クラスFunctionスニペット:

Python 3:

_class Function(object):
    . . .
    def __get__(self, obj, objtype=None):
        "Simulate func_descr_get() in Objects/funcobject.c"
        if obj is None:
            return self
        return types.MethodType(self, obj)
_

Python 2:

_class Function(object):
    . . .
    def __get__(self, obj, objtype=None):
        "Simulate func_descr_get() in Objects/funcobject.c"
        return types.MethodType(self, obj, objtype)
_
  1. クラスまたはインスタンスなしで関数が呼び出される場合、それは単純な関数です。
  2. 関数がクラスまたはインスタンスから呼び出されると、その___get___が呼び出されてラップされた関数を取得します。
    a。 _B.x_はB.__dict__['x'].__get__(None, B)と同じです。 Python 3、これは単純な関数を返します。Python 2、これは非バインド関数を返します。

    b。 _b.x_はtype(b).__dict__['x'].__get__(b, type(b)と同じです。これは、Python 2とPython 3の両方でバインドされたメソッドを返します。つまり、selfが暗黙的に最初の引数として渡されます。

2
lyu.l

関数、非バインドメソッド、バインドメソッドの違いは何ですか?

画期的な関数とは観点から違いはありません。 Pythonオブジェクト指向機能は、関数ベースの環境に基づいています。

制限されていることは次と等しい:

関数はclasscls)またはobjectインスタンスを取りますかself)最初のパラメーターとして、またはno?

以下に例を示します。

class C:

    #instance method 
    def m1(self, x):
        print(f"Excellent m1 self {self} {x}")

    @classmethod
    def m2(cls, x):
        print(f"Excellent m2 cls {cls} {x}")

    @staticmethod
    def m3(x):
        print(f"Excellent m3 static {x}")    

ci=C()
ci.m1(1)
ci.m2(2)
ci.m3(3)

print(ci.m1)
print(ci.m2)
print(ci.m3)
print(C.m1)
print(C.m2)
print(C.m3)

出力:

Excellent m1 self <__main__.C object at 0x000001AF40319160> 1
Excellent m2 cls <class '__main__.C'> 2
Excellent m3 static 3
<bound method C.m1 of <__main__.C object at 0x000001AF40319160>>
<bound method C.m2 of <class '__main__.C'>>
<function C.m3 at 0x000001AF4023CBF8>
<function C.m1 at 0x000001AF402FBB70>
<bound method C.m2 of <class '__main__.C'>>
<function C.m3 at 0x000001AF4023CBF8>

出力は、静的関数m3が呼び出されることはありませんboundC.m2は、クラスポインターであるCパラメーターを送信したため、clsクラスにバインドされます。

ci.m1およびci.m2は両方ともバインドされています。 ci.m1は、インスタンスへのポインタであるselfci.m2は、クラスがバインドされていることをインスタンスが知っているためです;)。

結論として、メソッドが取る最初のパラメーターに基づいて、メソッドをクラスまたはクラスオブジェクトにバインドできます。メソッドがバインドされていない場合、メソッドはアンバインドと呼ばれます。


メソッドは元々クラスの一部ではないかもしれないことに注意してください。詳細については、 this Alex Martelliからの回答を確認してください。

1
prosti