Frage an python, metaprogramming – Überschreiben spezieller Methoden für eine Instanz

21

Ich hoffe jemand kann das beantworten, der ein gutes tiefes Verständnis von Python hat :)

Betrachten Sie den folgenden Code:

<code>>>> 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>'
>>>
</code>

Beachten Sie, dass repr (a) nicht das erwartete Ergebnis von "A" liefert. Ich möchte wissen, warum dies der Fall ist und ob es einen Weg gibt, dies zu erreichen ...

Im Gegensatz dazu funktioniert das folgende Beispiel jedoch (Vielleicht, weil wir nicht versuchen, eine spezielle Methode zu überschreiben?):

<code>>>> 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'
>>>
</code>
Ich konnte nicht beschreiben, was ich im Detail tun möchte. Es tut uns leid. Kurz gesagt, ich versuche ein Prototyp-OO-Modell zu modellieren, in dem ich Operatoren wie: World = Object (). Clone (). Mixin (World); Wobei World eine Klasse mit einer Sammlung von Methoden ist, die eine in der Object.clone () - Instanz überschreiben / ersetzen. James Mills
Sie werden wahrscheinlich eine viel aufschlussreichere Antwort erhalten, wenn Sie sagen, was Sie letztendlich erreichen wollen. mattmanser
Bei Klassen neuen Stils werden spezielle Methoden für die Klasse und nicht für die Instanz nachgeschlagen. Weitere Details und einige Problemumgehungen finden Sie in meiner Antwort unten. Raymond Hettinger

Deine Antwort

4   die antwort
3

__x__()) sind für die Klasse definiert, nicht für die Instanz.

Dies ist sinnvoll, wenn Sie darüber nachdenken__new__() - Es ist unmöglich, dies für eine Instanz aufzurufen, da die Instanz beim Aufruf nicht vorhanden ist.

Sie können dies also für die gesamte Klasse tun, wenn Sie Folgendes möchten:

<code>>>> A.__repr__ = __repr__
>>> a
A
</code>

Oder im Einzelfall alsin kindall Antwort. (Beachten Sie, dass es hier viele Ähnlichkeiten gibt, aber ich dachte, dass meine Beispiele genug hinzugefügt wurden, um dies auch zu posten).

10

Spezielle Methodensuche:

Bei benutzerdefinierten Klassen funktioniert der implizite Aufruf spezieller Methoden nur dann ordnungsgemäß, wenn er für einen Objekttyp und nicht für das Objektinstanzverzeichnis definiert wurde. Zusätzlich zur Umgehung von Instanzattributen im Interesse der Korrektheit wird bei der impliziten Suche nach speziellen Methoden im Allgemeinen auch die Option umgangen__getattribute__() Methode auch der Metaklasse des Objekts

(Der Teil, den ich herausgeschnitten habe, erklärt die Gründe dafür, wenn Sie daran interessiert sind.)

Python dokumentiert nicht genau, wann eine Implementierung die Methode für den Typ nachschlagen sollte oder nicht. Alles, was es dokumentiert, ist, dass Implementierungen in der Instanz nach speziellen Methodensuchen suchen können oder nicht, daher sollten Sie auch nicht damit rechnen.

Wie Sie anhand Ihrer Testergebnisse erraten können, wird in der CPython-Implementierung__repr__ Ist eine der Funktionen die auf dem Typ nachgeschlagen wird.

In 2.x sind die Dinge etwas anders, hauptsächlich aufgrund der Präsenz klassischer Klassen. Solange Sie jedoch nur Klassen neuen Stils erstellen, können Sie sich diese als gleich vorstellen.

Der häufigste Grund, warum dies gewünscht wird, besteht darin, verschiedene Instanzen eines Objekts mit Affen zu patchen, um verschiedene Aktionen auszuführen. Sie können das nicht mit speziellen Methoden tun, also ... was können Sie tun? Es gibt eine saubere und eine hackige Lösung.

Die saubere Lösung besteht darin, eine spezielle Methode für die Klasse zu implementieren, die nur eine reguläre Methode für die Instanz aufruft. Dann können Sie diese reguläre Methode auf jeder Instanz mit Affen patchen. Zum Beispiel:

<code>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)
</code>

Die Hacky-Lösung besteht darin, eine neue Unterklasse im Handumdrehen zu erstellen und das Objekt neu zu klassifizieren. Ich vermute, dass jeder, der wirklich eine Situation hat, in der dies eine gute Idee ist, weiß, wie man es aus diesem Satz umsetzt, und jeder, der nicht weiß, wie man das macht, sollte es nicht versuchen, also lasse ich es dabei.

Gibt es einen Grund, den Sie verwendengetattr(self, '_repr')() statt nurself._repr()? Nathan
Ich denke an 2.x arbeiten, müssen Sie möglicherweise anrufen__get__(c, type(c))Aber ich kann mich nicht sicher erinnern und habe keinen funktionierenden 2.x-Interpreter auf diesem Computer. Vielleicht ist es besser, ihn zu verwendentypes.MethodType stattdessen? abarnert
20

die mit Namen umgeben sind__ auf der Instanz, aber nur auf der Klasse, anscheinend um die Leistung zu verbessern. Es gibt also keine Möglichkeit zum Überschreiben__repr__() direkt auf eine Instanz und machen es funktioniert. Stattdessen müssen Sie so etwas tun:

<code>class A(object):
    def __repr__(self):
        return self._repr()
    def _repr(self):
        return object.__repr__(self)
</code>

Jetzt können Sie überschreiben__repr__() auf einer Instanz durch Ersetzen_repr().

Sehendiese. Die Regeln für Klassen im alten Stil sind etwas gelockert, aber Sie sollten Klassen im neuen Stil verwenden, und in Python 3 sind sie sowieso die einzigen. kindall
Das istnicht um Leistung. Spezielle Methoden werden für den Typ nachgeschlagen, um sicherzustellen, dass sowohl eine Instanz als auch der Typ die spezielle Operation unterstützen können. Zum Beispiel,Klassenobjekte sind haschbar. Sie haben ein__hash__ Methode. Wenn die Klasse ein implementiert__hash__ Methode, um Instanzen Hashable zu machen, dann würde diese Methode kollidieren, wennhash(ClassObject) würde die Methode direkt in der Klasse nachschlagen (die selbst eine Instanz eines Metatyps ist). Martijn Pieters
Es geht nicht in erster Linie darum, die Leistung zu verbessern. Wie in den Dokumenten heißt es: "Die Gründe für dieses Verhalten liegen in einer Reihe spezieller Methoden, wie z__hash__() und__repr__() die von allen Objekten, einschließlich Typobjekten, implementiert werden. Wenn die implizite Suche dieser Methoden den herkömmlichen Suchprozess verwendet, schlagen sie fehl, wenn sie für das Typobjekt selbst aufgerufen werden__getattribute__ ebenfalls überspringbar seinist Performance.) abarnert
"Python ruft keine speziellen Methoden auf, die in der Instanz von __ umrahmt sind, sondern nur in der Klasse, anscheinend um die Leistung zu verbessern" <- Haben Sie einen Verweis darauf? James Mills
Auch "Python ruft die speziellen Methoden nicht auf ..." ist nicht wahr. Python nichtGarantie um sie anzurufen. CPython hat eine bestimmte Liste von Fällen, in denen die normale Suche übersprungen wird. Diese Liste ist jedoch implementierungsspezifisch und nicht dokumentiert. Sie sollten sich also nicht darauf verlassen. Spezielle Methoden können in der Instanz nachgeschlagen werden oder nicht, stellen Sie also sicher, dass Ihr Code in beide Richtungen funktioniert. abarnert
4

die Instanzen umgeht. Hier ein Auszug ausdie Quelle:

<code>  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 */
</code>

Sie können entweder zu einer alten Klasse wechseln (nicht von erben)Objekt) oder Sie können der Klasse Dispatcher-Methoden hinzufügen (Methoden, die Lookups zurück an die Instanz weiterleiten). Ein Beispiel für Instanz-Dispatcher-Methoden finden Sie im Rezept unterhttp://code.activestate.com/recipes/578091

AFAIK: Wenn ich zur Verwendung von Old-Style-Klassen wechseln würde, würde dies nicht für Python 3 funktionieren. James Mills

Verwandte Fragen