TypeError bei zwei gleichen types

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
mm96
User
Beiträge: 30
Registriert: Donnerstag 26. November 2020, 23:24

Hallo zusammen,

ich les grad ein Beispiel über Klassen (s.u.).
Der Code erzeugt bei mir folgenden Error:

TypeError: unsupported operand type(s) for /: 'IntervalMath' and 'IntervalMath'

Ich kapier nicht, warum die Divison nicht funktioniert, die anderen Operationen aber anstandslos durchlaufen...

Code: Alles auswählen

class IntervalMath:
    def __init__(self, lower, upper):
        self.lo = float(lower)
        self.up = float(upper)
    
    def __add__(self, other):
        a, b, c, d = self.lo, self.up, other.lo, other.up
        return IntervalMath(a + c, b + d)
    
    def __sub__(self, other):
        a, b, c, d = self.lo, self.up, other.lo, other.up
        return IntervalMath(a - d, b - c)

    def __mul__(self, other):
        a, b, c, d = self.lo, self.up, other.lo, other.up
        return IntervalMath(min(a*c, a*d, b*c, b*d), max(a*c, a*d, b*c, b*d))
    
    def __div__(self, other):
        a, b, c, d = self.lo, self.up, other.lo, other.up
        # [c, d] cannot contain zero:
        if c*d <= 0:
            raise ValueError\
                (f'Interval {other} cannot be denominator because it contains zero')
        return IntervalMath(min(a/c, a/d, b/c, b/d), max(a/c, a/d, b/c, b/d))
    
    def __str__(self):
        return f'[{self.lo:g}, {self.up:g}]'

if __name__ == '__main__':
    I = IntervalMath
    a = I(-3, -2)
    b = I(4, 5)
    expr = 'a+b', 'a-b', 'a*b', 'a/b'
    for e in expr:
        print(f'{e} =', eval(e))
Viele Grüße
Benutzeravatar
sparrow
User
Beiträge: 4164
Registriert: Freitag 17. April 2009, 10:28

Es gibt keine magische Funktion, die __div__ heißt. Keine Ahnung, ob es die in Python 2 mal gab. Heute gibt es __truediv__ für / und __floordiv__ für //.
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@mm96: Man sollte keine kryptischen Abkürzungen verwenden. `lo` und `up` sollten wie die Argumente von der `__init__()` heissen.

Der Test mit ``c * d <= 0`` testet nicht das was die Ausnahmemeldung sagt. Wenn das wahr ist, bedeutet das nicht, das entweder `c` oder `d` 0 ist. Das kann auch einfach nur bedeuten das einer der Werte negativ ist. Und auf 0 braucht man auch gar nicht extra testen, denn das würde ja sowieso einen `ZeroDivisionError` auslösen. Das kann man dann auch einfach nutzen ohne einen extra Test und eine andere Ausnahme auszulösen.

`eval()` sollte man vermeiden wo es geht und es geht eigentlich fast überall.

Code: Alles auswählen

#!/usr/bin/env python3
from operator import add, mul, sub, truediv


class IntervalMath:
    def __init__(self, lower, upper):
        self.lower = float(lower)
        self.upper = float(upper)

    def __repr__(self):
        return f"{self.__class__.__name__}({self.lower!r}, {self.upper!r})"

    def __str__(self):
        return f'[{self.lower:g}, {self.upper:g}]'

    def __add__(self, other):
        return IntervalMath(self.lower + other.lower, self.upper + other.upper)

    def __sub__(self, other):
        return IntervalMath(self.lower - other.lower, self.upper - other.upper)

    def __mul__(self, other):
        values = [
            self.lower * other.lower,
            self.lower * other.upper,
            self.upper * other.lower,
            self.upper * other.upper,
        ]
        return IntervalMath(min(values), max(values))

    def __truediv__(self, other):
        values = [
            self.lower / other.lower,
            self.lower / other.upper,
            self.upper / other.lower,
            self.upper / other.upper,
        ]
        return IntervalMath(min(values), max(values))


def main():
    a = IntervalMath(-3, -2)
    b = IntervalMath(4, 5)
    for symbol, function in [
        ("+", add),
        ("-", sub),
        ("*", mul),
        ("/", truediv),
    ]:
        print(f"a{symbol}b = {function(a, b)}")


if __name__ == '__main__':
    main()
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
mm96
User
Beiträge: 30
Registriert: Donnerstag 26. November 2020, 23:24

Alles klar, vielen Dank!
Auch für die Umgehung des eval, werd ich mir merken.
Bei dem Teilen durch Null wollte man glaub testen, ob Null im Intervall liegt, ich hab mir aber die Intervallarithmetik dafür nicht genug angeschaut: Es soll wohl nur [a, b]/[c, d] vermieden werden, wenn 0 Element von [c, d] ist.

Danke nochmal!
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

@mm96: die Bedingung `c*d <= 0` ist schon korrekt, einfache zu verstehen wäre wohl `c <= 0 and d >= 0`.
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Ah, okay, dann würde ich den Intervalltyp wahrscheinlich um ein `__contains__` erweitern und den Test als ``0 in other`` ausdrücken. Oder zumindest `c` und `d` ”aussschreiben” und die Vergleichoperatoren verketten: ``other.lower <= 0 <= other.upper``.

Mit `__contains__()` und ``in``:

Code: Alles auswählen

#!/usr/bin/env python3
from operator import add, mul, sub, truediv


class IntervalMath:
    def __init__(self, lower, upper):
        self.lower = float(lower)
        self.upper = float(upper)

    def __repr__(self):
        return f"{self.__class__.__name__}({self.lower!r}, {self.upper!r})"

    def __str__(self):
        return f'[{self.lower:g}, {self.upper:g}]'

    def __contains__(self, value):
        return self.lower <= value <= self.upper

    def __add__(self, other):
        return IntervalMath(self.lower + other.lower, self.upper + other.upper)

    def __sub__(self, other):
        return IntervalMath(self.lower - other.lower, self.upper - other.upper)

    def __mul__(self, other):
        values = [
            self.lower * other.lower,
            self.lower * other.upper,
            self.upper * other.lower,
            self.upper * other.upper,
        ]
        return IntervalMath(min(values), max(values))

    def __truediv__(self, other):
        if 0 in other:
            raise ValueError(f"denominator {other!r} contains 0")

        values = [
            self.lower / other.lower,
            self.lower / other.upper,
            self.upper / other.lower,
            self.upper / other.upper,
        ]
        return IntervalMath(min(values), max(values))


def main():
    a, b = IntervalMath(-3, -2), IntervalMath(4, 5)
    for symbol, function in [
        ("+", add),
        ("-", sub),
        ("*", mul),
        ("/", truediv),
    ]:
        print(f"a{symbol}b = {function(a, b)}")


if __name__ == "__main__":
    main()
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
mm96
User
Beiträge: 30
Registriert: Donnerstag 26. November 2020, 23:24

Ah, das sieht auch gut aus, übernehm ich.

Eine Frage hätt ich noch zu Deiner Funktion __repr__:
Warum benutzt Du "{self.lower!r}" mit !r; ist es wichtig, dass der Wert hier als string übergeben wird?
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@mm96: Das "!r" sorgt dafür das auch bei diesem Wert die `repr()`- und nicht die `str()`-Repräsentation verwendet wird. Die können ja unterschiedlich sein und nur die `repr()`-Darstellung ”garantiert” wie das Ergebnis aussieht, also das es entweder als Python-Code darsgestellt wird, der wieder zum gleichen Wert führen würde, oder in “spitze Klammern“ eingefasst wenigstens genug Informationen zur Fehlersuche dargestellt werden. Ist theoretisch hier überflüssig weil man relativ sicher sein kann, dass es sich um Werte vom Typ `float` handelt. Andererseits wäre genau das ein Kritikpunkt, denn eigentlich sollte man diese Klasse auch mit `int` oder `Decimal` oder was auch immer sich wie eine Zahl verhält, verwendet werden können und die `__init__()` da nix umwandeln, denn da gehen dann eventuell Informationen verloren. Zum Beispiel wenn es mit `Decimal`-Objekten aufgerufen wird.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
mm96
User
Beiträge: 30
Registriert: Donnerstag 26. November 2020, 23:24

Ich glaub ich komm langsam drauf.
Wenn man also z. B. ne Formel in der __str__ ausgeben würde, wäre die Benutzung von repr() sinnvoller wenn das Ganze in der __repr__-Methode stattfindet, um auch noch Infos über das Objekt oder den Code zu bekommen.
Hab mal noch das Decimal-Modul angeschaut, wahrscheinlich würde es mehr Sinn machen, alles in Decimals statt in floats zu rechnen, je nach Genauigkeitsanspruch.

Danke für die Hinweise!
Benutzeravatar
Kebap
User
Beiträge: 686
Registriert: Dienstag 15. November 2011, 14:20
Wohnort: Dortmund

> wahrscheinlich würde es mehr Sinn machen, alles in Decimals statt in floats zu rechnen

Blackjack meint, dass du einfach mit dem (Typ) rechnen solltest, was an die Klasse übergeben wird, und nicht in __init__ den Typ verändern sollst.
MorgenGrauen: 1 Welt, 8 Rassen, 13 Gilden, >250 Abenteuer, >5000 Waffen & Rüstungen,
>7000 NPC, >16000 Räume, >200 freiwillige Programmierer, nur Text, viel Spaß, seit 1992.
Antworten