Python 3/Operaattoreiden kuormittaminen


Operaattoreiden kuormittaminen tarkoittaa operandien väliin tai eteen laitettavien operaattoreiden, kuten +, -, *, /, toteuttamista omille luokille. Olemme jo nähneet miten operaattoria + voi käyttää paitsi numeroiden yhteenlaskuun myös yhdistämään kaksi listaa tai merkkijonoa ja operaattoreita * ja ** voi käyttää paitsi kertolaskuun ja potensiin korottamiseen myös monikon ja sanakirjan hajottamiseen funktion argumenteiksi.

Operaattori kuormitetaan toteuttamalla omassa oliossa erityinen metodi.

Kuormitettavat operaattorit
operaattori metodi
+ __add__(self, other)
__sub__(self, other)
* __mul__(self, other)
/ __truediv__(self, other)
// __floordiv__(self, other)
% __mod__(self, other)
** __pow__(self, other)
@ __matmul__(self, other)
>> __rshift__(self, other)
<< __lshift__(self, other)
& __and__(self, other)
| __or__(self, other)
^ __xor__(self, other)
< __lt__(self, other)
> __gt__(self, other)
<= __le__(self, other)
>= __ge__(self, other)
== __eq__(self, other)
!= __ne__(self, other)
__neg__(self)
+ __pos__(self)
~ __invert__(self)
in __contains__(self, key)
Kuormitettavat operaattorit
operaattori metodi
+= __iadd__(self, other)
-= __isub__(self, other)
*= __imul__(self, other)
/= __itruediv__(self, other)
//= __ifloordiv__(self, other)
%= __imod__(self, other)
**= __ipow__(self, other)
>>= __irshift__(self, other)
<<= __ilshift__(self, other)
&= __iand__(self, other)
= __ior__(self, other)
^= __ixor__(self, other)

Binaarisista operaattoresta on olemassa myös käänteinen versio, jossa nimen eteen tulee r.

Loogisia operaattoreita and, or ja not ei voi kuormittaa.

Esimerkki vertailuoperaattoreiden kuormittamisesta muokkaa

Tässä teemme tyypin kps, jolla voi esittää kivi–paperi–sakset-leikin arvoja ja vertailla niitä keskenään.

class kps:
    def __init__(self, val):
        self.val = val

    def __eq__(self, other):
        return (self.val == other.val)

    def __gt__(self, other):
        if self.val == "paperi" and other.val == "kivi":
            return True

        if self.val == "sakset" and other.val == "paperi":
            return True

        if self.val == "kivi" and other.val == "sakset":
            return True

        return False

    def __ge__(self, other):
        return self.__eq__(other) or self.__gt__(other)

    def __lt__(self, other):
        if self.val == "kivi" and other.val == "paperi":
            return True

        if self.val == "paperi" and other.val == "sakset":
            return True

        if self.val == "sakset" and other.val == "kivi":
            return True

        return False

    def __le__(self, other):
        return self.__eq__(other) or self.__lt__(other)

    def __repr__(self):
        return f'{self.val}'


kivi = kps("kivi")
paperi = kps("paperi")
sakset = kps("sakset")

if __name__ == "__main__":
    from itertools import combinations_with_replacement

    units = [kivi, paperi, sakset]
    pairs = [x for x in combinations_with_replacement(units, 2)]

    print("==")
    for pair in pairs:
        a = pair[0]
        b = pair[1]
        print(a, "==", b)
        print(a == b)
        print()

    print("!=")
    for pair in pairs:
        a = pair[0]
        b = pair[1]
        print(a, "!=", b)
        print(a != b)
        print()

    print(">")
    for pair in pairs:
        a = pair[0]
        b = pair[1]
        print(a, ">", b)
        print(a > b)
        print()

    print("<")
    for pair in pairs:
        a = pair[0]
        b = pair[1]
        print(a, "<", b)
        print(a < b)
        print()

    print(">=")
    for pair in pairs:
        a = pair[0]
        b = pair[1]
        print(a, ">=", b)
        print(a >= b)
        print()

    print("<=")
    for pair in pairs:
        a = pair[0]
        b = pair[1]
        print(a, "<=", b)
        print(a <= b)
        print()

Tulostaa

==
kivi == kivi
True

kivi == paperi
False

kivi == sakset
False

paperi == paperi
True

paperi == sakset
False

sakset == sakset
True

!=
kivi != kivi
False

kivi != paperi
True

kivi != sakset
True

paperi != paperi
False

paperi != sakset
True

sakset != sakset
False

>
kivi > kivi
False

kivi > paperi
False

kivi > sakset
True

paperi > paperi
False

paperi > sakset
False

sakset > sakset
False

<
kivi < kivi
False

kivi < paperi
True

kivi < sakset
False

paperi < paperi
False

paperi < sakset
True

sakset < sakset
False

>=
kivi >= kivi
True

kivi >= paperi
False

kivi >= sakset
True

paperi >= paperi
True

paperi >= sakset
False

sakset >= sakset
True

<=
kivi <= kivi
True

kivi <= paperi
True

kivi <= sakset
False

paperi <= paperi
True

paperi <= sakset
True

sakset <= sakset
True

Esimerkki bittioperaatoiden kuormittamisesta muokkaa

Alla olevassa esimerkissä teemme kolmiarvoista totuusarvoa esittävän luokan, jossa tosi- ja epätosi-arvojen lisäksi on epätietoisuutta esittävä arvo (?). Koska and-, or- ja not-operaattoreita ei voi kuormittaa käytämme niiden sijasta &-, |- ja ~-operaattoreita. Metodia __repr__ käsiteltiin osassa Luokka.

class tri:
    def __init__(self, val):
        self.__val = val

    @property
    def val(self):
        return self.__val

    def __and__(self, other):
        if self.val == "0" or other.val == "0":
            return tri("0")
        if self.val == "?" or other.val == "?":
            return tri("?")
        else:
            return tri("1")

    def __or__(self, other):
        if self.val == "1" or other.val == "1":
            return tri("1")
        elif self.val == "?" or other.val == "?":
            return tri("?")
        else:
            return tri("0")

    def __invert__(self):
        if self.val == "?":
            return tri("?")
        elif self.val == "1":
            return tri("0")
        else:
            return tri("1")

    def __repr__(self):
        return f"tri('{self.__val}')"


if __name__ == "__main__":
    from itertools import combinations_with_replacement
    values = [tri("0"), tri("?"), tri("1")]
    pairs = [x for x in combinations_with_replacement(values, 2)]

    print(f"{'NOT': ^21}")
    for a in values:
        print("~", a, "=", ~a)

    print()
    print(f"{'AND': ^30}")
    for a, b in pairs:
        print(a, "&", b, "=", a & b)

    print()
    print(f"{'OR': ^30}")
    for a, b in pairs:
        print(a, "|", b, "=", a | b)

Tulostaa

         NOT         
~ tri('0') = tri('1')
~ tri('?') = tri('?')
~ tri('1') = tri('0')

             AND              
tri('0') & tri('0') = tri('0')
tri('0') & tri('?') = tri('0')
tri('0') & tri('1') = tri('0')
tri('?') & tri('?') = tri('?')
tri('?') & tri('1') = tri('?')
tri('1') & tri('1') = tri('1')

              OR              
tri('0') | tri('0') = tri('0')
tri('0') | tri('?') = tri('?')
tri('0') | tri('1') = tri('1')
tri('?') | tri('?') = tri('?')
tri('?') | tri('1') = tri('1')
tri('1') | tri('1') = tri('1')

Harjoitukset muokkaa

  1. Tee murtolukua esittävä luokka ja toteuta sille ainakin operaattorit +, -, * ja /. Voit käyttä sieventämisessä apuna math-moduulin gcd-funktiota, joka palauttaa kahden annetun luvun suurimman yhteisen tekijän. Murtoluvuilla laskemista on käsitelty Matematiikan kirjassa.