Obsah
1. Hacky pro Python: vlastní infixové operátory
2. Přetěžování operátorů v Pythonu
3. Speciální metody použité při přetěžování operátorů
4. Ukázka realizace datového typu „komplexní číslo“
5. Realizace vlastního infixového operátoru přetížením dvojice standardních operátorů
7. První demonstrační příklad: třída HackyMul umožňující realizaci vlastního operátoru součinu
8. Odstranění ladicích informací, výpočet tabulky malé násobilky s využitím nového operátoru součinu
9. Praktičtější příklad: realizace operátoru pro skalární součin
10. Realizace nového operátoru s využitím znaku |
11. Demonstrační příklad: operátor skalární součinu zapisovaný znaky |o|
12. Problematika přetížení operátorů < a >
13. Korektní použití operátorů < a >
14. Univerzální konstruktor vlastních infixových operátorů
15. Sedmý demonstrační příklad: definice a použití vlastních infixových operátorů
16. Osmý demonstrační příklad: variabilní použití operátorů
17. Které znaky je možné použít v definovaných operátorech
19. Repositář s demonstračními příklady
1. Hacky pro Python: vlastní infixové operátory
V ekosystému programovacího jazyka Python nenajdeme jen samotnou specifikaci jazyka, několik jeho interpretrů a obrovské množství podpůrných nástrojů a knihoven. Součástí tohoto ekosystému jsou i více či méně formální doporučení, jakým způsobem se mají psát zdrojové kódy. Tato doporučení jsou do jisté míry validována různými nástroji typu Ruff, Pylint, PyRight, Pydocstyle atd. Ovšem to neznamená, že všechny zdrojové kódy psané v Pythonu musí být psány naprosto stejným stylem a podle jedné šablony. Python totiž umožňuje tvořit aplikace založené na objektově orientovaném programování, aplikace psané ve funkcionálním stylu atd. A navíc je možné (ve vhodných situacích) programovací jazyk „ohnout“ a využít jeho vlastností například pro vytvoření nových infixových operátorů (což zdánlivě není povoleno). Dnes si právě tento trik ukážeme a pochopitelně se zmíníme i o tom, zda je to vhodný či nevhodný přístup.
Jen pro ukázku, co je naším cílem: například se jedná o definici operátoru pro skalární součin:
x = [1, 2, 3] y = [3, 4, 5] print(x <<o>> y)
2. Přetěžování operátorů v Pythonu
Nejprve se ve stručnosti seznámíme s problematikou přetěžování standardních operátorů v Pythonu, což je technika, která nám umožní měnit chování operátorů, ovšem již neumožňuje nový operátor přidat. Tento programovací jazyk nabízí (pro různé datové typy) celou řadu operátorů, které jsou podle své priority rozděleny do osmnácti kategorií – od nejvyšší priority směrem k prioritě nejnižší:
Pořadí | Operátory |
---|---|
1. | (výrazy…), [výrazy…], {klíč: hodnota…}, {výrazy…} |
2. | x[index], x[index:index], x(argumenty…), x.atribut |
3. | await x |
4. | ** |
5. | +x, -x, ~x (unární) |
6. | *, @, /, //, % |
7. | +, – |
8. | <<, >> |
9. | & |
10. | ^ |
11. | | |
12. | in, not in, is, is not, <, <=, >, >=, !=, == |
13. | not x |
14. | and |
15. | or |
16. | if – else |
17. | lambda |
18. | := |
3. Speciální metody použité při přetěžování operátorů
Většinu operátorů zmíněných v předchozí kapitole je možné přetížit, tj. definovat jejich chování (jaký výpočet se provede) pro různé datové typy. Například je možné si nadefinovat datový typ představující vektory a definovat pro ně základní operátory (součet prvek po prvku atd.), totéž udělat pro matice, komplexní čísla atd. Vlastní předefinování chování operátoru je relativně snadné – pouze postačuje vytvořit speciální metodu, která bude zavolána v případě, že je operátor použit v programovém kódu. Tyto speciální metody mají v některých případech dvojí variantu – podle toho, na které straně operátoru se nachází operand příslušného typu (a toho později využijeme):
Přetížený operátor | Název speciální metody | Varianta s druhým operandem |
---|---|---|
+ | __add__ | __radd__ |
– | __sub__ | __rsub__ |
* | __mul__ | __rmul__ |
/ | __truediv__ | __rtruediv__ |
// | __floordiv__ | __rfloordiv__ |
% | __mod__ | __rmod__ |
** | __pow__ | __rpow__ |
+= | __iadd__ | |
-= | __isub__ | |
*= | __imul__ | |
/= | __itruediv__ | |
//= | __ifloordiv__ | |
%= | __imod__ | |
**= | __ipow__ | |
– (unární) | __neg__ | |
+ (unární) | __pos__ | |
< | __lt__ | |
<= | __le__ | |
== | __eq__ | |
!= | __ne__ | |
>= | __ge__ | |
> | __gt__ | |
& | __and__ | __rand__ |
| | __or__ | __ror__ |
^ | __xor__ | __rxor__ |
<< | __lshift__ | __rlshift__ |
>> | __rshift__ | __rrshift__ |
~ | __invert__ | |
&= | __iand__ | |
|= | __ior__ | |
^= | __ixor__ | |
<<= | __ilshift__ | |
>>= | __irshift__ | |
in | __contains__ |
4. Ukázka realizace datového typu „komplexní číslo“
Způsob přetížení některých vybraných unárních i binárních operátorů si ukážeme na příkladu komplexních čísel, přesněji řečeno na třídě Complex. V rámci definice této třídy jsou přetíženy operátory =, – (unární), +, – a +=. Implementace by měla být ze zdrojového kódu snadno pochopitelná:
class Complex: def __init__(self, real=0, imag=0): self._real = real self._imag = imag def __str__(self): return "{r} + {i}j".format(r=self._real, i=self._imag) def __eq__(self, other): """Přetížení binárního operátoru ==.""" return self._real == other._real and self._imag == other._imag def __neg__(self): """Přetížení unárního operátoru -.""" return Complex(-self._real, -self._imag) def __add__(self, other): """Přetížení binárního operátoru +.""" r = self._real + other._real i = self._imag + other._imag return Complex(r, i) def __sub__(self, other): """Přetížení binárního operátoru -.""" r = self._real - other._real i = self._imag - other._imag return Complex(r, i) def __iadd__(self, other): """Přetížení binárního operátoru +=.""" self._real += other._real self._imag += other._imag return self c1 = Complex(1, 2) c2 = Complex(10, 20) c3 = Complex(100) c4 = Complex() print("c1:", c1) print("c2:", c2) print("c3:", c3) print("c4:", c4) print() print("-c1:", -c1) print("-c2:", -c2) print() print("c1 + c2:", c1+c2) print("c1 + c3:", c1+c3) print("c1 - c2:", c1-c2) print("c1 - c3:", c1-c3) c2 += c3 print("c2 += c3:", c1) print() c5 = Complex(1, 2) print("c1 == c5:", c1 == c5) print("c2 == c5:", c2 == c5) print("c3 == c5:", c3 == c5)
Vypočtené a vypsané výsledky ukazují, že přetížené operátory pracují korektně:
c1: 1 + 2j c2: 10 + 20j c3: 100 + 0j c4: 0 + 0j -c1: -1 + -2j -c2: -10 + -20j c1 + c2: 11 + 22j c1 + c3: 101 + 2j c1 - c2: -9 + -18j c1 - c3: -99 + 2j c2 += c3: 1 + 2j c1 == c5: True c2 == c5: False c3 == c5: False
5. Realizace vlastního infixového operátoru přetížením dvojice standardních operátorů
Přetížení operátorů samo o sobě neumožňuje vytvoření nového operátoru. Co ovšem můžeme udělat, je vhodným způsobem přetížit dvojici existujících operátorů, například << a >>, a to takovým způsobem, že jejich kombinací (s vhodným „mezioperandem“) jakoby vznikne nový operátor. Příkladem je zápis uvedený už v první kapitole:
x = [1, 2, 3] y = [3, 4, 5] print(x <<o>> y)
Co ale vlastně zápis:
x <<o>> y
znamená? Dekódovat ho můžeme následovně:
- Provedení operace x << o, kde x je seznam a o je vhodným způsobem připravený objekt. Výsledkem je nějaká hodnota h.
- Provedení operace h >> y, kde h je hodnota z předchozího kroku a y je seznamem. Výsledek je současně i výsledkem celého zápisu x <<o>> y.
6. Způsob implementace
Musíme tedy vytvořit objekt o, jehož třída má přetížený operátor <<. Výsledná hodnota h musí být instancí třídy s přetíženým operátorem >>. Ovšem situaci si můžeme zjednodušit – pokud bude výsledkem první operace x << o opět instance stejné třídy, bude nám postačovat implementovat jen jedinou třídu, ve které budou přetíženy oba operátory << i >>. První přetížený operátor si zapamatuje svůj levý operand a po zavolání druhého operátoru se vyhodnotí výsledek celé operace. Budeme tedy muset definovat speciální metody __rlshift__ (operátor << s prohozenými operandy) a __rshift__ (operátor >>):
class NewOperator: def __rlshift__(self, other): self.value = other return self def __rshift__(self, other): # provedení výpočtu se self.value a other # vrácení výsledku tohoto výpočtu
7. První demonstrační příklad: třída HackyMul umožňující realizaci vlastního operátoru součinu
V dnešním prvním demonstračním příkladu je ukázán výše popsaný postup, který umožňuje realizaci vlastního operátoru násobení (resp. přesněji řečeno součinu), který se bude zapisovat pomocí symbolů <<x>> (namísto x bude ovšem možné použít libovolný jiný validní identifikátor). Celý trik, na kterém je tento příklad postaven, je založen na třídě pojmenované HackyMul, která vhodným způsobem přetíží operátory << a >> tak, aby se při aplikaci prvního operátoru zapamatoval levý operand (pravým operandem je instance této třídy) a aplikací druhého operátoru se vypočte součin, jehož hodnota se následně vrátí. Ve skutečnosti tedy zápis <<x>> obsahuje dva operátory. Navíc se v obou speciálních metodách vždy vypíše předaný parametr, aby bylo zřejmé, jak výpočet interně probíhá:
# Vlastní infixové operátory v Pythonu # # - třída HackyMul umožňující definici operátoru typu <<x>> apod. # - při volání speciálních metod se vypisují ladicí informace class HackyMul: def __rlshift__(self, other): print("rlshift:", other) self.value = other return self def __rshift__(self, other): print("rshift: ", other) return self.value * other x = HackyMul() print(2 <<x>> 3) y = HackyMul() print(3 <<y>> 3) z = HackyMul() i = 10 j = 20 print(i <<z>> j)
Po spuštění skriptu se můžeme přesvědčit, že výsledky skutečně odpovídají běžnému součinu:
rlshift: 2 rshift: 3 6 rlshift: 3 rshift: 3 9 rlshift: 10 rshift: 20 200
8. Odstranění ladicích informací, výpočet tabulky malé násobilky s využitím nového operátoru součinu
Funkce print byly ve speciálních metodách __rlshift__ a __rshift__ volány pouze z toho důvodu, abychom si ujasnili, za jakých okolností a s jakými argumenty jsou tyto metody volány. V praxi se pochopitelně bez těchto pomocných výpisů většinou obejdeme a proto je ve druhém příkladu z definice třídy HackyMul odstraníme. Aby bylo zřejmé, že součin je počítán korektně, necháme si v dnešním druhém demonstračním příkladu vypočítat tabulku malé násobilky. Implementace tohoto demonstračního příkladu bude vypadat následovně:
# Vlastní infixové operátory v Pythonu # # - třída HackyMul umožňující definici operátoru typu <<x>> apod. # - bez výpisu ladicích informací # - výpočet tabulky malé násobilky přes nový "operátor" class HackyMul: def __rlshift__(self, other): self.value = other return self def __rshift__(self, other): return self.value * other x = HackyMul() for j in range(1, 11): for i in range(1, 11): r = i <<x>> j print(f"{r:3d}", end=" ") print()
Z tabulky zobrazené níže je patrné, že výpočty s využitím našeho operátoru <<x>> probíhají korektně:
1 2 3 4 5 6 7 8 9 10 2 4 6 8 10 12 14 16 18 20 3 6 9 12 15 18 21 24 27 30 4 8 12 16 20 24 28 32 36 40 5 10 15 20 25 30 35 40 45 50 6 12 18 24 30 36 42 48 54 60 7 14 21 28 35 42 49 56 63 70 8 16 24 32 40 48 56 64 72 80 9 18 27 36 45 54 63 72 81 90 10 20 30 40 50 60 70 80 90 100
9. Praktičtější příklad: realizace operátoru pro skalární součin
Realizace vlastního operátoru pro provedení běžného součinu je pochopitelně pouze ukázka toho, jakým směrem je možné do jisté míry „ohnout“ syntaxi programovacího jazyka Python. Ukažme si tedy poněkud praktičtější příklad. Tím bude realizace operátoru pro výpočet skalárního součinu (dot product). Náš nový speciální operátor bude předpokládat, že na jeho levé i pravé straně budou jako operandy předány n-tice nebo seznamy mající stejný počet prvků. Operátor vrátí výsledek skalárního součinu obou operandů (ty jsou považovány za vektory délky n):
# Vlastní infixové operátory v Pythonu # # - třída DotProduct umožňující definici operátoru pro skalární součin # - definice nového operátoru # - otestování nového operátoru class DotProduct: def __rlshift__(self, other): self.value = other return self def __rshift__(self, other): dot_product = 0 for a, b in zip(self.value, other): dot_product += a * b return dot_product o = DotProduct() x = [1, 2, 3] y = [3, 4, 5] print(x <<o>> y)
Po spuštění tohoto skriptu si ověříme, zda je výpočet korektní či nikoli. Měla by se vypsat hodnota 26, která je získána výpočtem 1×3+2×4+3×5=3+8+15:
26
Způsob přetížení operátoru @ a popř. @= může vypadat následovně:
# Operátor maticového násobení: # - přetížení operátoru maticového násobení @ # - přetížení operátoru @= kombinujícího maticové násobení s přiřazením class Test: """Třída s přetíženými operátory @ a @=.""" def __init__(self, x): """Konstruktor.""" self.value = str(x) def __str__(self): """Metoda volaná při tisku objektu.""" return self.value def __matmul__(self, other): """Metoda přetěžující operátor @.""" return Test(f"({self.value} @ {other.value})") def __imatmul__(self, other): """Metoda přetěžující operátor @=.""" return Test(f"({self.value} @ {other.value})") # konstrukce několika instancí třídy Test t1 = Test(1) print("t1:", t1) t2 = Test(2) print("t2:", t2) t3 = Test(3) print("t3:", t3) # použití obou přetížených operátorů ve stejném příkazu t3 @= t1 @ t2 print("t3 @= t1 @ t2:", t3)
10. Realizace nového operátoru s využitím znaku |
Samozřejmě nám nic nebrání v tom, aby nový operátor nebyl vytvořen ze znaků << a >>. Můžeme využít jakékoli jiné existující operátory. Například se může jednat o operátor |. Zde ovšem vyvstává otázka, jak v zápisu:
x |o| y
rozlišit první a druhý operátor, které se zapisují stejným znakem. Ve skutečnosti je to v tomto případě jednoduché, protože operátor | přetížíme pro náš objekt o (resp. pro třídu, které je o instancí) a v ní již můžeme snadno rozlišit „pravou“ a „levou“ variantu operátoru, tedy situaci, kdy se o nachází v pozici pravého nebo levého operandu. Definovat tedy budeme muset metody __ror__ (pravá varianta) a __or__ (levá varianta):
class NewOperator: def __ror__(self, other): self.value = other return self def __or__(self, other): # provedení výpočtu se self.value a other # vrácení výsledku tohoto výpočtu
11. Demonstrační příklad: operátor skalární součinu zapisovaný znaky |o|
V dalším demonstračním příkladu je ukázána realizace nového infixového operátoru, který bude zapisován s využitím znaků |o|. Od předchozích skriptů se tento příklad odlišuje tím, že jsou definovány speciální metody __ror__ a __or__. Musí se přitom definovat obě zmíněné metody, protože první z těchto metod je volána tehdy, pokud je instance třídy DotProduct pravým operandem a druhá metoda je volána naopak tehdy, pokud je instance DotProduct operandem levým:
# Vlastní infixové operátory v Pythonu # # - třída DotProduct umožňující definici operátoru pro skalární součin # - odlišné symboly operátoru # - definice nového operátoru # - otestování nového operátoru class DotProduct: def __ror__(self, other): self.value = other return self def __or__(self, other): dot_product = 0 for a, b in zip(self.value, other): dot_product += a * b return dot_product o = DotProduct() x = [1, 2, 3] y = [3, 4, 5] print(x |o| y)
Spuštěním tohoto příkladu si opět ověříme, jestli jsou provedeny korektní výpočty (mělo by tomu tak být):
26
12. Problematika přetížení operátorů < a >
Prozatím jsme pro nové „operátory“ využívali existující operátory <<, >> a taktéž operátor |. Tento výběr nebyl náhodný, protože výsledkem byly vizuálně odlišné (de facto) nové prvky jazyka. Ovšem nic nám nebrání ani v tom, abychom pro stejný účel použili jiné standardní operátory. Řekněme, že si vybereme dvojici operátorů < a > (tedy nezdvojených variant). Mohlo by se zdát, že v tomto případě nám postačuje přetížit skutečně oba dva operátory, tj. definovat metody __lt__ a __gt__. Ve skutečnosti nebude toto řešení funkční, o čemž se můžeme velmi snadno přesvědčit:
# Vlastní infixové operátory v Pythonu # # - třída DotProduct umožňující definici operátoru pro skalární součin # - pokus o redefinici speciálních metod pro operátory < a > # - nekorektní řešení! class DotProduct: def __lt__(self, other): self.value = other return self def __gt__(self, other): dot_product = 0 for a, b in zip(self.value, other): dot_product += a * b return dot_product o = DotProduct() x = [1, 2, 3] y = [3, 4, 5] print(x <o> y)
Výsledkem bude chyba:
Traceback (most recent call last): File "/home/ptisnovs/src/most-popular-python-libs/custom_operators/infix_5.py", line 25, in <module> print(x <o> y) ^^^^^^^ File "/home/ptisnovs/src/most-popular-python-libs/custom_operators/infix_5.py", line 16, in __gt__ for a, b in zip(self.value, other): ^^^^^^^^^^ AttributeError: 'DotProduct' object has no attribute 'value'
Tato chyba vznikla tím, že se vlastně vůbec nezavolala metoda __lt__! Je tomu tak z toho důvodu, že x < o sice používá operátor <, ovšem Python nemá přetíženou metodu __lt__ pro seznam (levý operand). Proto zavolá metodu __gt__ objektu o (nemáme totiž k dispozici variantu __rlt__).
13. Korektní použití operátorů < a >
Jedno z možných řešení problému zmíněného v předchozí kapitole spočívá v tom, že se přetíží pouze metoda __gt__ (ta je volaná dvakrát), ovšem interně se musíme rozhodnout, zda se jedná o první volání nebo o volání druhé. Na základě tohoto rozhodnutí si buď zapamatujeme levý operand, nebo provedeme výpočet skalárního součinu. Jedno z možných (nutno říci, že poměrně škaredých) řešení je ukázáno v dalším příkladu:
# Vlastní infixové operátory v Pythonu # # - třída DotProduct umožňující definici operátoru pro skalární součin # - redefinice speciální metody pro operátor > # - korektní řešení class DotProduct: def __init__(self): self.value = None def __gt__(self, other): if self.value is None: print("<", other) self.value = other return self print(">", other) dot_product = 0 for a, b in zip(self.value, other): dot_product += a * b return dot_product o = DotProduct() x = [1, 2, 3] y = [3, 4, 5] print(x <o> y)
Nyní by již vše mělo být funkční:
< [1, 2, 3] > [3, 4, 5] 26
14. Univerzální konstruktor vlastních infixových operátorů
Ve všech předchozích příkladech jsme museli pro každý nový „operátor“ definovat novou třídu. Nabízí se ovšem otázka, zda není možné napsat třídu univerzální, tj. takovou třídu, do které by se pouze předala nějaká funkce určená pro implementaci požadované operace. Toto řešení je pochopitelně možné realizovat a jedno z možných řešení je ukázáno níže (pro operátor složený ze znaků << a >>). První polovina operátoru vrací novou instanci třídy s uzávěrem, který si kromě jiného pamatuje i předaný levý operand other. A druhá polovina operátoru provede vlastní výpočet zavoláním uzávěru:
class Infix: def __init__(self, function): self.function = function def __rlshift__(self, other): # uzávěr return Infix(lambda x, self=self, other=other: self.function(other, x)) def __rshift__(self, other): # levá hodnota je již dostupná v uzávěru return self.function(other)
Příklad použití (tedy definice nového operátoru s jeho zavoláním):
x=Infix(lambda x,y: x*y) print(2 <<x>> 4)
15. Sedmý demonstrační příklad: definice a použití vlastních infixových operátorů
Výše uvedený koncept si můžeme otestovat a to hned pro několik různých uživatelem definovaných operátorů:
# Vlastní infixové operátory v Pythonu # # - třída Infix umožňující definici jakéhokoli binárního operátoru # - definice několika operátorů # - otestování nových operátorů class Infix: def __init__(self, function): self.function = function def __rlshift__(self, other): # uzávěr return Infix(lambda x, self=self, other=other: self.function(other, x)) def __rshift__(self, other): # levá hodnota je již dostupná v uzávěru return self.function(other) # operátor součinu print("multiply:") x=Infix(lambda x,y: x*y) print(2 <<x>> 4) print("-" * 20) # operátor testu typu hodnoty print("'is a' and 'instance of':") isa=Infix(lambda x,y: x.__class__==y.__class__) print([1,2,3] <<isa>> []) # operátor testu, zda je hodnota instancí třídy instance_of=Infix(lambda x,y: isinstance(x, y)) print(1 <<instance_of>> int) print(1 <<instance_of>> str) print("foo" <<instance_of>> str) print(1 <<instance_of>> int and "foo" <<instance_of>> str) print("-" * 20) print("has attr:") has_attr=Infix(lambda x,y: hasattr(x, y)) print(str <<has_attr>> "tolower") print(str <<has_attr>> "tolowercase") print(str <<has_attr>> "lower") print(str <<has_attr>> "__doc__") print("-" * 20) print("has method:") has_method=Infix(lambda x,y: hasattr(x, y) and callable(getattr(x, y, None))) print(str <<has_method>> "tolower") print(str <<has_method>> "tolowercase") print(str <<has_method>> "lower") print(str <<has_method>> "__doc__") print("-" * 20) print("is in:") is_in=Infix(lambda x,y: x in y) print(1 <<is_in>> {1:'one'}) print(1 <<is_in>> {1:'one'}) print("-" * 20) print("gcd:") import math gcd=Infix(lambda x,y: math.gcd(x, y)) print(12 <<gcd>> 8) print("div:") import operator div=Infix(operator.floordiv) print(10 <<div>> (4 <<div>> 2))
Jak je ze zdrojového kódu patrné, vytvořili jsme si hned několik vlastních operátorů, které byly použity. Test, zda vše pracuje tak, jak má:
multiply: 8 -------------------- 'is a' and 'instance of': True True False True True -------------------- has attr: False False True True -------------------- has method: False False True False -------------------- is in: True True -------------------- gcd: 4 div: 5
16. Osmý demonstrační příklad: variabilní použití operátorů
Nic nám pochopitelně nebrání v tom, abychom uživatelům umožnili pro vlastní operátory používat jak dvojice znaků << a >>, tak i například již zmíněný znak |. Pouze rozšíříme třídu Infix o další dvě speciální metody __ror__ a __or__:
# Vlastní infixové operátory v Pythonu # # - třída Infix umožňující definici jakéhokoli binárního operátoru # - definice několika operátorů # - otestování nových operátorů class Infix: def __init__(self, function): self.function = function def __rlshift__(self, other): # uzávěr return Infix(lambda x, self=self, other=other: self.function(other, x)) def __rshift__(self, other): # levá hodnota je již dostupná v uzávěru return self.function(other) def __ror__(self, other): return self.__rlshift__(other) def __or__(self, other): return self.__rshift__(other) # operátor součinu print("multiply:") x=Infix(lambda x,y: x*y) print(2 |x| 4) print("-" * 20) # operátor testu typu hodnoty print("'is a' and 'instance of':") isa=Infix(lambda x,y: x.__class__==y.__class__) print([1,2,3] |isa| []) # operátor testu, zda je hodnota instancí třídy instance_of=Infix(lambda x,y: isinstance(x, y)) print(1 |instance_of| int) print(1 |instance_of| str) print("foo" |instance_of| str) print(1 |instance_of| int and "foo" |instance_of| str) print("-" * 20) print("has attr:") has_attr=Infix(lambda x,y: hasattr(x, y)) print(str |has_attr| "tolower") print(str |has_attr| "tolowercase") print(str |has_attr| "lower") print(str |has_attr| "__doc__") print("-" * 20) print("has method:") has_method=Infix(lambda x,y: hasattr(x, y) and callable(getattr(x, y, None))) print(str |has_method| "tolower") print(str |has_method| "tolowercase") print(str |has_method| "lower") print(str |has_method| "__doc__") print("-" * 20) print("is in:") is_in=Infix(lambda x,y: x in y) print(1 |is_in| {1:'one'}) print(1 |is_in| {1:'one'}) print("-" * 20) print("gcd:") import math gcd=Infix(lambda x,y: math.gcd(x, y)) print(12 |gcd| 8) print("div:") import operator div=Infix(operator.floordiv) print(10 |div| (4 |div| 2))
Výsledky by přitom měly být stejné, jako tomu bylo v předchozím demonstračním příkladu.
17. Které znaky je možné použít v definovaných operátorech
Možná se na tomto místě můžete zeptat, že když už Python „ohýbáme“ do takové míry, že si definujeme vlastní (pseudo)operátory, jestli je možné tyto operátory pojmenovat s využitím speciálních Unicode symbolů, tedy například symbolem ×, ∃, ∊ atd. V původním Pythonu 2 to nebylo vůbec možné (byli jsme omezeni ASCII znaky), ovšem v Pythonu 3 můžeme využít alespoň některé znaky (ovšem většinu z nich nikoli na první pozici identifikátoru):
Z tabulky Unicode znaků lze pro první znak identifikátoru vybírat ze skupin:
Lu - uppercase letters Ll - lowercase letters Lt - titlecase letters Lm - modifier letters Lo - other letters
Pro druhý a další znak se navíc jedná o skupiny:
Nl - letter numbers Mn - nonspacing marks Mc - spacing combining marks Nd - decimal numbers Pc - connector punctuations
18. Možné problémy
Řešení nebo možná lépe řečeno hack, který byl v tomto článku popsán, může být v mnoha ohledech problematické, zejména tehdy, pokud nedodržíme přesný zápis operátoru (což je vlastně výraz se dvěma operátory). Problémem jsou zde kryptická chybová hlášení. Taktéž je zapotřebí si dát velký pozor na prioritu operátorů, protože náš „složený operátor“ může mít menší prioritu, než další části výrazu:
o = DotProduct() x = [1, 2, 3] y = [3, 4, 5] print(x + y |o| y + x)
Tento skript vypíše hodnotu 52, protože se provede:
print((x + y) |o| (y + x))
19. Repositář s demonstračními příklady
Všechny demonstrační příklady, které jsme si dnes ukázali, jsou uloženy v repositáři https://github.com/tisnik/most-popular-python-libs. Následují odkazy na jednotlivé příklady:
# | Příklad | Stručný popis | Adresa příkladu |
---|---|---|---|
1 | infix1.py | třída HackyMul umožňující definici operátoru typu <<x>> apod. s výpisem ladicích informací | https://github.com/tisnik/most-popular-python-libs/blob/master/custom_operators/infix1.py |
2 | infix2.py | třída HackyMul umožňující definici operátoru typu <<x>> apod. bez výpisu ladicích informací | https://github.com/tisnik/most-popular-python-libs/blob/master/custom_operators/infix2.py |
3 | infix3.py | realizace nového operátoru pro skalární součin, založeno na operátorech << a >> | https://github.com/tisnik/most-popular-python-libs/blob/master/custom_operators/infix3.py |
4 | infix4.py | realizace nového operátoru pro skalární součin, založeno na operátoru | | https://github.com/tisnik/most-popular-python-libs/blob/master/custom_operators/infix4.py |
5 | infix5.py | realizace nového operátoru pro skalární součin, založeno na operátorech < a > (nekorektní varianta) | https://github.com/tisnik/most-popular-python-libs/blob/master/custom_operators/infix5.py |
6 | infix6.py | realizace nového operátoru pro skalární součin, založeno na operátorech < a > (korektní varianta) | https://github.com/tisnik/most-popular-python-libs/blob/master/custom_operators/infix6.py |
7 | infix7.py | implementace třídy pro konstrukci vlastních operátorů (jednodušší varianta) | https://github.com/tisnik/most-popular-python-libs/blob/master/custom_operators/infix7.py |
8 | infix8.py | implementace třídy pro konstrukci vlastních operátorů (nepatrně složitější varianta) | https://github.com/tisnik/most-popular-python-libs/blob/master/custom_operators/infix8.py |
9 | complex.py | implementace komplexních čísel i s přetížením vybraných operátorů | https://github.com/tisnik/most-popular-python-libs/blob/master/custom_operators/complex.py |
20. Odkazy na Internetu
- Python: defining my own operators?
https://stackoverflow.com/questions/932328/python-defining-my-own-operators - Infix operators (Python recipe)
https://web.archive.org/web/20220528202902/https://code.activestate.com/recipes/384122/ - Sage: decorators
https://doc.sagemath.org/html/en/reference/misc/sage/misc/decorators.html - make a custom infix operator decorator
https://github.com/sagemath/sage/issues/6245 - Creating Custom Operators in Python 3
https://dnmtechs.com/creating-custom-operators-in-python-3/ - Allowed characters in Python function names
https://stackoverflow.com/questions/19482730/allowed-characters-in-python-function-names - List of Unicode Characters of Category “Other Letter”
https://www.compart.com/en/unicode/category/Lo - Python Unicode Variable Names
https://www.asmeurer.com/python-unicode-variable-names/ - Start characters
https://www.asmeurer.com/python-unicode-variable-names/start-characters.md - Continue characters
https://www.asmeurer.com/python-unicode-variable-names/continue-characters.md - Special Methods
https://www.pythonlikeyoumeanit.com/Module4_OOP/Special_Methods.html - 3. Data model
https://docs.python.org/3/reference/datamodel.html - Special Method Names
https://diveintopython3.net/special-method-names.html - Full list of Python special method names?
https://stackoverflow.com/questions/51562492/full-list-of-python-special-method-names - Python special methods
https://pythonforstarters.solomonmarvel.com/object-oriented-programming-in-python/python-special-methods - Python's Magic Methods: Leverage Their Power in Your Classes
https://realpython.com/python-magic-methods/