22

2 つの呼び出し可能なオブジェクトが同じかどうかをテストできるようにしたいと考えています。私は同一性セマンティクス (「is」演算子を使用) を好みますが、メソッドが関係する場合、別のことが起こることを発見しました。

#(1) identity and equality with a method
class Foo(object):
    def bar(self):
        pass
foo = Foo()
b = foo.bar
b == foo.bar    #evaluates True. why?
b is foo.bar    #evaluates False. why?

古いバージョンの実装の詳細ではないことを確認するために、Python 2.7 と 3.3 (CPython) の両方でこれを再現しました。それ以外の場合、ID テストは期待どおりに機能します (インタープリター セッションは上から続きます)。

#(2) with a non-method function
def fun(self):
    pass
f = fun
f == fun    #evaluates True
f is fun    #evaluates True

#(3) when fun is bound as a method 
Foo.met = fun
foo.met == fun    #evaluates False
foo.met is fun    #evaluates False

#(4) with a callable data member
class CanCall(object):
    def __call__(self):
        pass
Foo.can = CanCall()
c = foo.can
c == foo.can    #evaluates True
c is foo.can    #evaluates True

質問によると、Pythonはクラスのメンバーであるコールバック関数をどのように区別しますか? 、関数はメソッドとしてバインドされるとラップされます。これは理にかなっており、上記のケース (3) と一致しています。

メソッドを他の名前にバインドし、後でそれらを呼び出し可能オブジェクトまたはプレーン関数のように同等に比較する信頼できる方法はありますか? 「==」がうまくいくとしたら、それはどのように機能するのでしょうか? 上記(1)の場合、「==」と「is」の動作が異なるのはなぜですか?

編集

@Claudiu が指摘したように、Why don't Methods have reference equal?への答え この質問に対する答えでもあります。

4

4 に答える 4

16

Python は、 classfoo.barのすべてのインスタンスに対して標準オブジェクトを保持しません。代わりに、Python が を評価するときにメソッド オブジェクトが作成されます。したがって、fooFoofoo.bar

foo.bar is not foo.bar

に関しては==、物事が乱雑になります。Python 3.8 ではメソッドの比較が修正されたため、2 つのメソッドが同じオブジェクトの同じメソッドを表す場合は同等になりますが、下位バージョンでは動作に一貫性がありません。

Python には、メソッドが Python で実装されたか、C でメソッドを実装できるいくつかの方法のいずれかによって、驚くほど多くのメソッド オブジェクト タイプがあります==

  • Pythonで記述されたメソッドの場合、メソッドと属性を==比較し、メソッド オブジェクトが同じ関数によって実装され、同じオブジェクトではなく等しいオブジェクトにバインドされたメソッドを表す場合に True を返します。したがって、andが Python で記述されている場合は True になります。__func____self__x.foo == y.foox == yfoo
  • ほとんどの「特別な」メソッド ( __eq____repr__など) について、それらが C で実装されている場合、Pythonは と__self__に類似した内部的なものを比較し__func__、メソッドが同じ実装を持ち、等しいオブジェクトにバインドされている場合、再び True を返します。
  • C で実装された他のメソッドの場合、Python は実際に期待することを行い、メソッド オブジェクトが同じオブジェクトの同じメソッドを表す場合に True を返します。

したがって、3.8 より前のバージョンの Python で次のコードを実行すると、次のようになります。

class Foo(object):
    def __eq__(self, other):
        return True if isinstance(other, Foo) else NotImplemented
    def foo(self):
        pass

print(Foo().foo == Foo().foo)
print([].__repr__ == [].__repr__)
print([].append == [].append)

次の奇妙な出力が得られます。

True
True
False

下位バージョンで Python 3.8 セマンティクスを取得するには、次を使用できます。

meth1.__self__ is meth2.__self__ and meth1 == meth2
于 2013-08-13T18:59:12.340 に答える
1

tldr:メソッドは記述子であるため、これが発生する可能性があります。==本当に等しいかどうかを比較する必要がある場合に使用します。

is(事実上) が等しいかどうかをテストしidます。それでは、それをチェックしてみましょう:

>>> id(foo.bar)
4294145364L
>>> id(foo.bar)
4294145364L
>>> id(foo.bar)
4294145364L
>>> b = foo.bar
>>> id(foo.bar)
4293744796L
>>> id(foo.bar)
4293744796L
>>> b()
>>> id(foo.bar)
4293744796L
>>> b = 1
>>> id(foo.bar)
4294145364L
>>> type(foo.bar)
<type 'instancemethod'>
>>>

したがって、直接の原因は、式がfoo.bar断続的に別のオブジェクトを返すことです。

等しいかどうかを確認する必要がある場合は、 を使用してください==。しかし、私たちは皆、この問題の根底に到達したいと考えています。

>>> foo.__dict__['bar']
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'bar'
>>> Foo.__dict__['bar']
<function bar at 0xffe2233c>
>>> getattr(foo, 'bar')
<bound method Foo.bar of <__main__.Foo object at 0xffe2f9ac>>
>>> foo.bar
<bound method Foo.bar of <__main__.Foo object at 0xffe2f9ac>>
>>>

バインドされたメソッドには何か特別なものがあるようです。

>>> type(foo.bar)
<type 'instancemethod'>
>>> help(type(foo.bar))
Help on class instancemethod in module __builtin__:

class instancemethod(object)
 |  instancemethod(function, instance, class)
 |
 |  Create an instance method object.
 |
 |  Methods defined here:
 |
 |  __call__(...)
 |      x.__call__(...) <==> x(...)
 |
 |  __cmp__(...)
 |      x.__cmp__(y) <==> cmp(x,y)
 |
 |  __delattr__(...)
 |      x.__delattr__('name') <==> del x.name
 |
 |  __get__(...)
 |      descr.__get__(obj[, type]) -> value
 |
 |  __getattribute__(...)
 |      x.__getattribute__('name') <==> x.name
 |
 |  __hash__(...)
 |      x.__hash__() <==> hash(x)
 |
 |  __repr__(...)
 |      x.__repr__() <==> repr(x)
 |
 |  __setattr__(...)
 |      x.__setattr__('name', value) <==> x.name = value
 |
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |
 |  __func__
 |      the function (or other callable) implementing a method
 |
 |  __self__
 |      the instance to which a method is bound; None for unbound methods
 |
 |  im_class
 |      the class associated with a method
 |
 |  im_func
 |      the function (or other callable) implementing a method
 |
 |  im_self
 |      the instance to which a method is bound; None for unbound methods
 |
 |  ----------------------------------------------------------------------
 |  Data and other attributes defined here:
 |
 |  __new__ = <built-in method __new__ of type object>
 |      T.__new__(S, ...) -> a new object with type S, a subtype of T

ここで、__get__メソッドがリストされていることに注意してください。つまり、instancemethodオブジェクトは記述子です。http://docs.python.org/2/reference/datamodel.html#implementing-descriptorsに従って、式foo.barは の結果を返します(getattr(foo,'bar').__get__(foo)。そのため、この値変更される可能性があります。

なぜ変更されるのかについて、実装の詳細である可能性が高いことを除いて、私には言えません。

于 2013-08-13T19:09:47.027 に答える
0

foo is bar同じことを使用して、id(foo) == id(bar)身元を確認できます。「等しい」(値) をチェックしたい場合は、 を使用します==

于 2013-08-13T23:18:04.430 に答える