Operatoren umdefinieren

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
nezzcarth
User
Beiträge: 1634
Registriert: Samstag 16. April 2011, 12:47

Hallo :)

ich hatte letztens ein Beispiel in einer anderen Sprache gesehen, wo jemand Operatoren umdefiniert hatte, sodass (zum Beispiel, aber nicht zwingend) 5 + 5 keine Addition, sondern eine Subtraktion ausführt und damit 0 ergibt. Mal abgesehen davon, dass das natürlich nicht sehr sinnvoll und eher Spielerei ist, würde ich trotzdem gerne wissen, wie man das in Python machen würde ;)

Da isinstance(5, int) True ergibt und mein bisheriges Verständnis so war, dass - wegen des Duck Typings ? - der Ablauf c.a. so sein müsste, dass bei Verwendung des Operators '+' die entsprechende __add__ Funktion des Objektes aufgerufen würde, dachte ich es würde reichen, die entsprechenden Methoden der Klasse 'int' zu überschreiben.

Folgendes veranlasst mich aber dazu anzunehmen, dass ich da irgendwas noch nicht richtig verstanden habe:

(Python 3.3)

Code: Alles auswählen

from operator import add, sub, mul, truediv
>>> isinstance(5, int)
True
class int(int):
    def __add__(self, y):
        return sub(self.numerator, y)
    def __sub__(self, y):
        return add(self.numerator, y)
    def __mul__(self, y):
        return truediv(self.numerator, y)
    def __div__(self, y):
        return mul(self.numerator, y)

>>> 5 + 6
11
>>> m, n = int(5), int(6)
>>> m + n
-1
>>> isinstance(5, int)
False
Offensichtlich habe ich da was nicht richtig verstanden - was denn?
Werden Zahlen, die man ohne Aufruf von int() an Namen bindet also nicht "unter der Haube" doch an diese übergeben?
Und wie wäre es richtig?

Vielen Dank schon mal. :)
BlackJack

@nezzcarth: Literale werden nicht durch die entsprechende Funktion erstellt sondern direkt. Und bei den eingebauten Typen kann man die Attribute auch nicht überschreiben. Das was Du vorhast geht also nicht.

Code: Alles auswählen

In [1]: int.__add__ = 42
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
/home/bj/<ipython-input-1-ad98d128de62> in <module>()
----> 1 int.__add__ = 42

TypeError: can't set attributes of built-in/extension type 'int'
nezzcarth
User
Beiträge: 1634
Registriert: Samstag 16. April 2011, 12:47

@BlackJack: Danke dir für deine rasche Rückmeldung. Ist wahrscheinlich auch besser so, dass das nicht geht; sonderlich sinnvoll und "sauberem" Code zuträglich ist das ja nicht gerade ;)
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Du müsstest den Rückgabewerte deiner Methoden jeweils an eine neue Instanz deiner Klasse übergeben, ansonsten werden Built-In Typen zurückgeliefert, da die importierten Funktionen aus dem `operator`-Modul nicht auf magische Art wissen können, dass sie den Typen deiner eigenen Klasse zurückgeben sollen. Wenn du nicht einfach `int` überschreiben würdest, dann wäre das vielleicht nicht ganz so verwirrend. Zudem brauchst du `.numerator` nicht unbedingt, genau so wenig wie die importierten Funktionen, weil man ja direkt die Operatoren verwenden kann. Letztlich könnte dein Beispiel (gekürzt) dann so aussehen:

Code: Alles auswählen

>>> class CrazyInteger(int):
...     def __add__(self, other):
...         return CrazyInteger(self - other)
... 
>>> result = CrazyInteger(5) + CrazyInteger(6)
>>> result
-1
>>> isinstance(result, CrazyInteger)
True
EDIT: Damit das auch mit Vererbung funktioniert, ist es übrigens besser, wenn `.__add__()` die Instanz vom "eigenen" Typen etwas abstrakter erstellt:

Code: Alles auswählen

class CrazyInteger(int):
    def __add__(self, other):
        return self.__class__(self - other)
EDIT2: Achso, das Hauptanliegen der Frage war wohl eher, ob die eigene `int`-Klasse aufgerufen wird. Naja, das hatte BlackJack ja schon beantwortet...
nezzcarth
User
Beiträge: 1634
Registriert: Samstag 16. April 2011, 12:47

@snafu
Das mit '.numerator' wusste ich nicht, danke für den Hinweis. (Ich hatte nur kurz per dir geschaut, wie ich denn dann jetzt an die Zahl rankomme, und das gefunden - dabei scheint das streng genommen eigentlich für was anderes zu sein, sehe ich gerade). Die Operatoren hatte ich importiert, weil ich mir dachte, dass ich nicht dieselben, an deren Semantik ich gerade rumfummele, verwenden kann; tatsächlich führt

Code: Alles auswählen

class int(int):
    def __add__(self, other):
        return self - other
    def __sub__(self, other):
        return self + other
dann auch zu einer Endlosrekursion.

Insgesamt wird das alles, wie du ja auch sagtest, sehr schnell sehr verwirrend, was wohl ein weiteres Indiz dafür ist, dass man das bleiben lassen sollte ;)
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Also gehen tut das wohl schon, man kann die libpython mit ctypes laden und dort die Sachen modifizieren bis sie so funktionieren. Ist aber kein Feature was die Sprache unterstützt.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Ja, das mit der Rekursion hatte ich übersehen... :oops:
Antworten