27

Pythonを深く理解している人がこれに答えてくれることを願っています:)

次のコードを検討してください。

>>> class A(object):
...     pass
...
>>> def __repr__(self):
...     return "A"
...
>>> from types import MethodType
>>> a = A()
>>> a
<__main__.A object at 0x00AC6990>
>>> repr(a)
'<__main__.A object at 0x00AC6990>'
>>> setattr(a, "__repr__", MethodType(__repr__, a, a.__class__))
>>> a
<__main__.A object at 0x00AC6990>
>>> repr(a)
'<__main__.A object at 0x00AC6990>'
>>>

repr(a)が「A」の期待される結果を生成しないことに注意してください。なぜこれが当てはまるのか、そしてこれを達成する方法があるかどうかを知りたいです...

対照的に、次の例は機能します(おそらく、特別なメソッドをオーバーライドしようとしていないためですか?):

>>> class A(object):
...     def foo(self):
...             return "foo"
...
>>> def bar(self):
...     return "bar"
...
>>> from types import MethodType
>>> a = A()
>>> a.foo()
'foo'
>>> setattr(a, "foo", MethodType(bar, a, a.__class__))
>>> a.foo()
'bar'
>>>
4

5 に答える 5

29

Pythonは通常、インスタンスで名前が囲まれ__ている特別なメソッドを呼び出しませんが、クラスでのみ呼び出します。(これは実装の詳細ですが、標準のインタープリターであるCPythonの特徴です。)したがって__repr__()、インスタンスを直接オーバーライドして機能させる方法はありません。代わりに、次のようなことを行う必要があります。

class A(object):
    def __repr__(self):
        return self._repr()
    def _repr(self):
        return object.__repr__(self)

__repr__()これで、を代入してインスタンスをオーバーライドできます_repr()

于 2012-04-29T22:42:30.703 に答える
11

特別なメソッドルックアップで説明されているように:

カスタムクラスの場合、特殊メソッドの暗黙的な呼び出しは、オブジェクトのインスタンスディクショナリではなく、オブジェクトのタイプで定義されている場合にのみ正しく機能することが保証されます…正確さのためにインスタンス属性をバイパスすることに加えて、暗黙の特殊メソッドルックアップは通常、__getattribute__()オブジェクトのメタクラスのメソッドでも

(私が切り取った部分は、あなたがそれに興味があるなら、これの背後にある理論的根拠を説明しています。)

Pythonは、実装が型のメソッドを検索する必要があるかどうかを正確に文書化しません。事実上、実装がインスタンスを参照して特別なメソッドルックアップを行う場合と行わない場合があることを文書化しただけなので、どちらも当てにしないでください。

テスト結果から推測できるように、CPython実装で__repr__は、型で検索される関数の1つです。


2.xでは、主にクラシッククラスが存在するため、状況が少し異なりますが、新しいスタイルのクラスのみを作成している限り、それらは同じものと考えることができます。


人々がこれをしたい最も一般的な理由は、オブジェクトのさまざまなインスタンスにモンキーパッチを適用してさまざまなことを行うことです。特別な方法ではそれができないので…何ができるでしょうか?クリーンなソリューションとハッキーなソリューションがあります。

クリーンな解決策は、インスタンスで通常のメソッドを呼び出すだけの特別なメソッドをクラスに実装することです。次に、各インスタンスでその通常のメソッドにモンキーパッチを適用できます。例えば:

class C(object):
    def __repr__(self):
        return getattr(self, '_repr')()
    def _repr(self):
        return 'Boring: {}'.format(object.__repr__(self))

c = C()
def c_repr(self):
    return "It's-a me, c_repr: {}".format(object.__repr__(self))
c._repr = c_repr.__get__(c)

ハッキーな解決策は、その場で新しいサブクラスを構築し、オブジェクトを再分類することです。これが良い考えであるという状況を本当に持っている人は誰でもその文からそれを実装する方法を知っていると思います、そしてそれを行う方法を知らない人は試みるべきではないので、私はそれをそのままにしておきます。

于 2014-09-26T01:28:34.710 に答える
4

この理由は__x__()、インスタンスではなくクラスに対して特別なメソッド()が定義されているためです。

これは、考えてみると理にかなっ__new__()ています。インスタンスが呼び出されたときにインスタンスが存在しないため、インスタンスでこれを呼び出すことは不可能です。

したがって、必要に応じて、クラス全体でこれを行うことができます。

>>> A.__repr__ = __repr__
>>> a
A

または、kindallの回答のように、個々のインスタンスで。(ここには多くの類似点があることに注意してください、しかし私は私の例がこれを投稿するのに十分追加されたと思いました)。

于 2012-04-29T22:37:38.163 に答える
3

新しいスタイルクラスの場合、Pythonはインスタンスをバイパスする特別なメソッドルックアップを使用します。ここにソースからの抜粋があります:

  1164 /* Internal routines to do a method lookup in the type
  1165    without looking in the instance dictionary
  1166    (so we can't use PyObject_GetAttr) but still binding
  1167    it to the instance.  The arguments are the object,
  1168    the method name as a C string, and the address of a
  1169    static variable used to cache the interned Python string.
  1170 
  1171    Two variants:
  1172 
  1173    - lookup_maybe() returns NULL without raising an exception
  1174      when the _PyType_Lookup() call fails;
  1175 
  1176    - lookup_method() always raises an exception upon errors.
  1177 
  1178    - _PyObject_LookupSpecial() exported for the benefit of other places.
  1179 */

古いスタイルのクラスに変更するか(オブジェクトから継承しない)、ディスパッチャーメソッドをクラスに追加することができます(ルックアップをインスタンスに転送するメソッド)。インスタンスディスパッチャメソッドの例については、http://code.activestate.com/recipes/578091のレシピを参照してください。

于 2012-04-29T23:10:40.427 に答える
0

TLDR:インスタンスに適切なバインドされていないメソッドを定義することは不可能です。これは特別な方法にも当てはまります。バインドされたメソッドはファーストクラスのオブジェクトであるため、特定の状況では違いは目立ちません。ただし、特別なメソッドは、必要に応じてPythonによって常に適切なバインドされていないメソッドとして検索されます。

より一般的な属性アクセスを使用する特別なメソッドにいつでも手動でフォールバックできます。属性アクセスは、属性として格納されているバインドされたメソッドと、必要に応じてバインドされているバインドされていないメソッドの両方を対象としています。__repr__これは、または他のメソッドが属性を使用して出力を定義する方法と似ています。

class A:
    def __init__(self, name):
        self.name = name

    def __repr__(self):
        # call attribute to derive __repr__
        return self.__representation__()

    def __representation__(self):
        return f'{self.__class__.__name__}({self.name})'

    def __str__(self):
        # return attribute to derive __str__
        return self.name

アンバウンドメソッドとバウンドメソッド

Pythonのメソッドには、クラスの非バインドメソッドとそのクラスのインスタンスのバインドメソッドの2つの意味があります。

非バインドメソッドは、クラスまたはその基本クラスの1つに対する通常の関数です。クラス定義中に定義することも、後で追加することもできます。

>>> class Foo:
...     def bar(self): print('bar on', self)
...
>>> Foo.bar
<function __main__.Foo.bar(self)>

バインドされていないメソッドは、クラスに1回だけ存在します。これは、すべてのインスタンスで同じです。

バインドされたメソッドは、特定のインスタンスにバインドされたバインドされていないメソッドです。__get__これは通常、関数のメソッドを呼び出すインスタンスを介してメソッドが検索されたことを意味します。

>>> foo = Foo()
>>> # lookup through instance
>>> foo.bar
<bound method Foo.bar of <__main__.Foo object at 0x10c3b6390>>
>>> # explicit descriptor invokation
>>> type(foo).bar.__get__(foo, type(Foo))
<bound method Foo.bar of <__main__.Foo object at 0x10c3b6390>>

Pythonに関する限り、「メソッド」とは通常、必要に応じてそのインスタンスにバインドされるバインドされていないメソッドを意味します。Pythonが特別なメソッドを必要とする場合、バインドされていないメソッドの記述子プロトコルを直接呼び出します。結果として、メソッドはクラスで検索されます。インスタンスの属性は無視されます。


オブジェクトのバインドされたメソッド

バインドされたメソッドは、インスタンスからフェッチされるたびに新しく作成されます。結果は、アイデンティティを持ち、保存して渡すことができ、後で呼び出すことができるファーストクラスのオブジェクトです。

>>> foo.bar is foo.bar  # binding happens on every lookup
False
>>> foo_bar = foo.bar   # bound methods can be stored
>>> foo_bar()           # stored bound methods can be called later
bar on <__main__.Foo object at 0x10c3b6390>
>>> foo_bar()
bar on <__main__.Foo object at 0x10c3b6390>

バインドされたメソッドを格納できるということは、それらを属性として格納することもできることを意味します。バインドされたメソッドをバインドされたインスタンスに格納すると、バインドされていないメソッドのように見えます。ただし、実際には、格納されたバインドされたメソッドの動作は微妙に異なり、属性を許可する任意のオブジェクトに格納できます。

>>> foo.qux = foo.bar
>>> foo.qux
<bound method Foo.bar of <__main__.Foo object at 0x10c3b6390>>
>>> foo.qux is foo.qux  # binding is not repeated on every lookup!
True
>>> too = Foo()
>>> too.qux = foo.qux   # bound methods can be stored on other instances!
>>> too.qux             # ...but are still bound to the original instance!
<bound method Foo.bar of <__main__.Foo object at 0x10c3b6390>>
>>> import builtins
>>> builtins.qux = foo.qux  # bound methods can be stored...
>>> qux                     # ... *anywhere* that supports attributes
<bound method Foo.bar of <__main__.Foo object at 0x10c3b6390>>

Pythonに関する限り、バインドされたメソッドは通常の呼び出し可能なオブジェクトです。の方法であるかどうかを知る方法がないのと同じように、それがtoo.qux方法であるtooかどうかを推測することもできませんtoo.__repr__

于 2019-10-21T12:37:55.017 に答える