{ "nbformat": 4, "nbformat_minor": 0, "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" }, "colab": { "name": "kl_py_oo_dekorator.ipynb", "provenance": [], "collapsed_sections": [], "include_colab_link": true } }, "cells": [ { "cell_type": "markdown", "metadata": { "id": "view-in-github", "colab_type": "text" }, "source": [ "\"Open" ] }, { "cell_type": "markdown", "metadata": { "id": "7rdi2v7RQIcz", "colab_type": "text" }, "source": [ "

\n", " \n", " \n", "

\n", "

\n", "

\n", "\n", "\n", "# Python alapok, objektumok\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "---" ] }, { "cell_type": "markdown", "metadata": { "id": "YfhHw47yQIc1", "colab_type": "text" }, "source": [ "---\n", "\n", "## Python Objektum orientálság szintaxisa\n", "\n", "\n", "A Python egy objektumorientált nyelv, „alapvetően” ugyanolyan utasítások vannak benne, mint a többi oo programnyelben. \n", "\n", "### Főbb szintaktikai különbségek:\n", "\n", "* A blokkokat `{` és `}` helyett beljebb kezdés jelöli (és egy kettőspont a blokkot nyitó utasítás végén).\n", "* Az utasítások végét a sorvége jelöli ki pontosvessző helyett.\n", "* Nem kell kiírni a változók típusát. A függvények definícióját `def` vezeti be.\n", "\n", "---" ] }, { "cell_type": "code", "metadata": { "id": "lBKm2msRQIc2", "colab_type": "code", "colab": {}, "outputId": "980317bc-4eea-4b07-af88-b42520e06902" }, "source": [ "def repeat(string):\n", " if \"kl\" in string:\n", " print('Jól van!')\n", " return string*2\n", " else:\n", " return string*4\n", "\n", "print(repeat(\"Négy \"))\n", "print(repeat(\"kl \"))" ], "execution_count": 0, "outputs": [ { "output_type": "stream", "text": [ "Négy Négy Négy Négy \n", "Jól van!\n", "kl kl \n" ], "name": "stdout" } ] }, { "cell_type": "markdown", "metadata": { "id": "RBUjDIYRQIc7", "colab_type": "text" }, "source": [ "Az osztályokat `class` vezeti be, a metódusokat osztályon belül `def`-fel kell definiálni.\n", "\n", "Bizonyos speciális funkciókat (konstruktor, operátorok felülírása) olyan metódusok látnak el, amelyeknek a neve két aláhúzásjellel kezdődik és végződik:" ] }, { "cell_type": "code", "metadata": { "id": "LdlNZmXSQIc8", "colab_type": "code", "colab": {}, "outputId": "98465e3e-cb54-4307-b835-f57cf18ba51c" }, "source": [ "class Person:\n", " def __init__(self, name, age):\n", " self.name = name\n", " self.age = age\n", " def __str__(self):\n", " return '{} ({})'.format(self.name, self.age)\n", " def do_something(self, thing):\n", " print(self, thing+'!')\n", "\n", "class Knight(Person):\n", " def __str__(self):\n", " return \"Sir \"+super().__str__()\n", "\n", "robin = Knight(\"Robin\", 42)\n", "robin.do_something(\"bátran elszaladt\")" ], "execution_count": 0, "outputs": [ { "output_type": "stream", "text": [ "Sir Robin (42) bátran elszaladt!\n" ], "name": "stdout" } ] }, { "cell_type": "markdown", "metadata": { "id": "BbtAdt-kQIc_", "colab_type": "text" }, "source": [ "---\n", "\n", "### Itt két dupla-aláhúzásjeles speciális nevet használtunk:\n", "\n", "\n", "* `__init__` az osztály konstruktora; új objektum létrehozásához egyszerűen meghívjuk paraméterekkel az osztályt (mint a 14. sorban) és ennek hatására hívja meg a rendszer az `__init__` metódust (ebben a példában a `Knight(...)` hívás a `Person`-tól örökölt `__init__`-et hívja).\n", "\n", "* `__str__` a sztring-konverzió metódus. Ezt automatikusan meghívja (többek között például) a beépített `print` függvény, aminek sztringgé kell alakítani a paramétereit, mielőtt a képernyőre írhatná azokat.\n", "\n", "\n", "---" ] }, { "cell_type": "markdown", "metadata": { "id": "-iuv52EfQIdA", "colab_type": "text" }, "source": [ "---\n", "\n", "### Dekorátorok\n", "\n", "Egy függvény definíciója (a `def` utasítás) két dolgot csinál: létrehoz egy függvény objektumot és azt eltárolja olyan néven, amit megadtunk. \n", "\n", "A dekorátorok lehetővé teszik, hogy valamit „beszúrjunk” eközé a két lépés közé: létrejön a függvény objektum, meghívódik a dekorátor és megkapja paraméterként az éppen létrejött függvény objektumot, majd a dekorátor visszatérési értéke eltárolódik olyan néven, amit a függvény definíciójánál megadtunk.\n", "\n", "---" ] }, { "cell_type": "markdown", "metadata": { "id": "ey6VLi9BQIdA", "colab_type": "text" }, "source": [ "### A dekorátorokat kukac karakterrel kell bevezetni:\n", "\n", "```python\n", "@callable_used_as_decorator\n", "def new_function(arguments):\n", " #... function body\n", "```\n", "\n", "\n", "Ahogyan fentebb leírtuk, ez **nagyjából** annak felel meg, mintha azt írtuk volna, hogy:\n", "\n", "```python\n", "def _temporary_function_object(arguments):\n", " #... function body\n", "new_function = callable_used_as_decorator(_temporary_function_object)\n", "```\n", "\n", "\n", "(Leszámítva persze azt, hogy a dekorátornál nem tárolódik el ideiglenes változóban (`_temporary_function_object`) a függvényünk.)\n", "\n", "---" ] }, { "cell_type": "markdown", "metadata": { "id": "NG5YaZTBQIdB", "colab_type": "text" }, "source": [ "Példaképpen ez a (gyakorlatban nem túl hasznos) függvény meghívja a megkapott függvény objektumot, majd módosítás nélkül visszakapja azt:" ] }, { "cell_type": "code", "metadata": { "id": "pmNT6M5QQIdC", "colab_type": "code", "colab": {} }, "source": [ "def run_immediately(func):\n", " func()\n", " return func" ], "execution_count": 0, "outputs": [] }, { "cell_type": "markdown", "metadata": { "id": "jUjrzESVQIdF", "colab_type": "text" }, "source": [ "Ha ezt dekorátorként használjuk, akkor az történik, amit ígértünk – a függvény azonnal lefut a definiálása után és később úgy használható, mintha mi sem történt volna:" ] }, { "cell_type": "code", "metadata": { "id": "r3kI89SAQIdG", "colab_type": "code", "colab": {}, "outputId": "b7ec29ac-a30b-4d7d-9d37-1b8f0aca8367" }, "source": [ "@run_immediately\n", "def greet():\n", " print(\"Üdvözöllek, dicső lovag!\")\n", " \n", "print(\"Tram Tram tatam\")\n", "greet()\n", "greet()" ], "execution_count": 0, "outputs": [ { "output_type": "stream", "text": [ "Üdvözöllek, dicső lovag!\n", "Tram Tram tatam\n", "Üdvözöllek, dicső lovag!\n", "Üdvözöllek, dicső lovag!\n" ], "name": "stdout" } ] }, { "cell_type": "markdown", "metadata": { "collapsed": true, "id": "pvn7zmByQIdJ", "colab_type": "text" }, "source": [ "---\n", "\n", "Általában azonban olyan dolgokat akarunk dekorátorként használni, amelyek valahogy módosítják az éppen definiált függvényt.\n", "\n", "A függvény „módosítására” két módszer is van, először azt mutatjuk be, ami C++-ban is létezik (viszont Pythonban ritkábban használt).\n", "\n", "\n", "Itt azt csináljuk, hogy valójában nem is függvényt adunk vissza, hanem egy olyan objektumot, ami függvényként használható, mert felülírta a függvényhívás operátort (van neki `__call__` metódusa):\n", "\n", "---" ] }, { "cell_type": "code", "metadata": { "id": "1pLEvVvAQIdK", "colab_type": "code", "colab": {}, "outputId": "c514da8b-370e-481a-8dd5-c845dad6907b" }, "source": [ "class cached:\n", " def __init__(self, func):\n", " self.func = func\n", " self.cache = {} # üres hash-tábla\n", " def __call__(self, arg):\n", " try:\n", " return self.cache[arg]\n", " except KeyError:\n", " result = self.cache[arg] = self.func(arg)\n", " return result \n", "\n", "@cached\n", "def ask_for_value(name):\n", " return input(name+\" értéke? \")\n", "\n", "print(ask_for_value)\n", "\n", "results = []\n", "results.append(ask_for_value(\"x\"))\n", "results.append(ask_for_value(\"y\"))\n", "results.append(ask_for_value(\"x\"))\n", "print(results) " ], "execution_count": 0, "outputs": [ { "output_type": "stream", "text": [ "<__main__.cached object at 0x0000014904918248>\n", "x értéke? 2\n", "y értéke? 3\n", "['2', '3', '2']\n" ], "name": "stdout" } ] }, { "cell_type": "markdown", "metadata": { "id": "1PYlpGe1QIdM", "colab_type": "text" }, "source": [ "---\n", "\n", "Ahogyan láthatjuk, `ask_for_value` néven valójában nem egy függvény, hanem egy `cached` típusú objektum van eltárolva, ami azt csinálja, amit szeretnénk: bekéri `x` értékét, bekéri `y` értékét, majd nem kéri be újra `x` értékét, mert azt már megmondtuk neki.\n", "\n", "\n", "\n", "#### További megjegyzések:\n", "\n", "* Ez a `cached` implementáció csak egyparaméteres függvényeket kezel, de nem lett volna sokkal bonyolultabb olyat írni, ami akárhány paramétert kezel.\n", "* Itt találkoztunk még egy dupla-aláhúzásjeles névvel: a `__call__` metódus akkor hívódik meg, ha a megfelelő típusú objektumot függvényként kezeljük és paramétereket adunk át neki.\n", "\n", "\n", "\n", "\n", "A második megoldás azon alapul, hogy Pythonban „jól” lehet függvényen belül függvényeket definiálni: Ha a belső függvény használja a külső függvény egy lokális változóját, akkor egy [closure](https://en.wikipedia.org/wiki/Closure_%28computer_programming%29) jön létre és a belső függvény „elkapja és magával viszi” azokat a lokális változókat.\n", "\n", "Ennek a bonyolult folyamatnak az az eredménye, hogy a következő kód ugyanúgy gyorsítótárazást valósít meg, mint az előző példa:\n", "\n", "\n", "---" ] }, { "cell_type": "code", "metadata": { "id": "111jEMHlQIdN", "colab_type": "code", "colab": {}, "outputId": "cc188829-7da2-4230-c63c-49c8fb9d9e91" }, "source": [ "def cached(func):\n", " cache = {}\n", " def wrapper(arg):\n", " try:\n", " return cache[arg]\n", " except KeyError:\n", " result = cache[arg] = func(arg)\n", " return result\n", " return wrapper\n", "\n", "@cached\n", "def ask_for_value(name):\n", " return input(name+\" értéke? \")\n", "\n", "print(ask_for_value)\n", "\n", "results = []\n", "results.append(ask_for_value(\"x\"))\n", "results.append(ask_for_value(\"y\"))\n", "results.append(ask_for_value(\"x\"))\n", "print(results) " ], "execution_count": 0, "outputs": [ { "output_type": "stream", "text": [ ".wrapper at 0x00000149048F53A8>\n", "x értéke? 2\n", "y értéke? 3\n", "['2', '3', '2']\n" ], "name": "stdout" } ] }, { "cell_type": "markdown", "metadata": { "id": "uGmfHFlzQIdQ", "colab_type": "text" }, "source": [ "---\n", "\n", "Vegyük észre, hogy mostmár valóban egy függvény típusú dolog van eltárolva az `ask_for_value` néven, azonban ennek a metaadatai (például neve) nem stimmelnek.\n", "\n", "Ennek az esztétikai problémának a korrigálására lehet importálni a `functools.wraps` függvényt, ami helyreteszi a metaadatokat:\n", "\n", "---" ] }, { "cell_type": "code", "metadata": { "scrolled": false, "id": "CWC-HiOQQIdR", "colab_type": "code", "colab": {}, "outputId": "fe5c546f-7b4b-4b54-d8d7-b19eed6c0433" }, "source": [ "import functools\n", "\n", "def cached(func):\n", " cache = {}\n", " @functools.wraps(func)\n", " def wrapper(arg):\n", " try:\n", " return cache[arg]\n", " except KeyError:\n", " result = cache[arg] = func(arg)\n", " return result\n", " return wrapper\n", "\n", "@cached\n", "def ask_for_value(name):\n", " '''Így szokás dokumentációt írni Pythonban.'''\n", " return input(name+\" értéke? \")\n", "\n", "print(ask_for_value)\n", "print(\"Fontos metaadatok:\")\n", "print(\"Név:\", ask_for_value.__name__)\n", "print(\"Dokumentáció:\", ask_for_value.__doc__)\n", "\n", "#futtatás kihagyva, ugyanúgy működne, mint előbb" ], "execution_count": 0, "outputs": [ { "output_type": "stream", "text": [ "\n", "Fontos metaadatok:\n", "Név: ask_for_value\n", "Dokumentáció: Így szokás dokumentációt írni Pythonban.\n" ], "name": "stdout" } ] }, { "cell_type": "markdown", "metadata": { "id": "BUNsubK4QIdU", "colab_type": "text" }, "source": [ "Persze kézzel is átállítgathattuk volna a metaadatokat (aki nagyon kíváncsi: [itt a functools modul forráskódja](https://github.com/python/cpython/blob/master/Lib/functools.py)), de ez így elegánsabb és rövidebb kód." ] }, { "cell_type": "markdown", "metadata": { "id": "f08eiJ2JQIdV", "colab_type": "text" }, "source": [ "Ez a példa illusztrálja, hogy a dekorátor kijelölésekor lehet adattag-elérést (pont operátor) és függvényhívást alkalmazni. (Más operátorokat viszont nem, lásd a `def` utasítás leírását a [Python Language Reference](https://docs.python.org/3/reference/compound_stmts.html#function-definitions)-ben.)\n", "\n", "Pontosabban fogalmazva `functools.wraps` nem egy dekorátor, hanem egy _dekorátor factory_: paraméterül kap egy függvényt (ahonnan veszi a metaadatok értékeit) és a visszatérési értékét fogjuk dekorátorként használni." ] }, { "cell_type": "markdown", "metadata": { "id": "Lll1-t9VQIdV", "colab_type": "text" }, "source": [ "Mi is tudunk ilyen dekorátor factory-t írni, bár ehhez kicsit sok egymásba ágyazott függvény fog kelleni. A példánk azt valósítja meg, hogy a dekorált függvény minden meghívása után az eredmény legyen naplózva (egy bizonyos fájlba, bizonyos üzenettel felcímkézve):" ] }, { "cell_type": "code", "metadata": { "id": "IIMLW37gQIdW", "colab_type": "code", "colab": {}, "outputId": "ec27e3b6-e981-42d7-c52d-ed2240bdf1b0" }, "source": [ "from functools import wraps\n", "\n", "def logged(file, msg):\n", " def decorator(func):\n", " @wraps(func)\n", " def wrapper(*args, **kw):\n", " result = func(*args, **kw)\n", " file.write(msg + str(result) + \"\\n\")\n", " return result\n", " return wrapper\n", " return decorator\n", "\n", "import sys\n", "\n", "@logged(sys.stderr, \"Osztás eredménye: \") ## Rózsszinezés\n", "def divide(x, y):\n", " return x/y\n", "\n", "[divide(2,2), divide(16,-8), divide(1,8)]" ], "execution_count": 0, "outputs": [ { "output_type": "stream", "text": [ "Osztás eredménye: 1.0\n", "Osztás eredménye: -2.0\n", "Osztás eredménye: 0.125\n" ], "name": "stderr" }, { "output_type": "execute_result", "data": { "text/plain": [ "[1.0, -2.0, 0.125]" ] }, "metadata": { "tags": [] }, "execution_count": 26 } ] }, { "cell_type": "markdown", "metadata": { "id": "X26JgbpKQIdZ", "colab_type": "text" }, "source": [ "*Itt sys.stderr a sztenderd hiba kimenet, amit békés rózsaszín háttérrel jelenít meg a Jupyter rendszer.*" ] }, { "cell_type": "markdown", "metadata": { "id": "Yzz4MR4xQIda", "colab_type": "text" }, "source": [ "Ezt is megvalósíthatjuk _closure_-ök nélkül, C++ stílusban, de ehhez meglehetősen sokat kell írni:" ] }, { "cell_type": "code", "metadata": { "id": "WZJ6TxZOQIdb", "colab_type": "code", "colab": {}, "outputId": "a55399ad-5c24-46a2-8d2b-b6680fdeba91" }, "source": [ "class ResultLogger:\n", " def __init__(self, func, file, msg):\n", " self.func = func\n", " self.file = file\n", " self.msg = msg\n", " def __call__(self, *args, **kw):\n", " result = self.func(*args, **kw)\n", " self.file.write(self.msg + str(result) + \"\\n\")\n", " return result\n", "\n", "class logged:\n", " def __init__(self, file, msg):\n", " self.file = file\n", " self.msg = msg\n", " def __call__(self, func):\n", " return ResultLogger(func, self.file, self.msg)\n", "\n", "import sys\n", "\n", "@logged(sys.stderr, \"Összeadás eredménye: \")\n", "def add(x, y):\n", " return x+y\n", "\n", "[add(2,2), add(6,-8), add(3,3)]" ], "execution_count": 0, "outputs": [ { "output_type": "stream", "text": [ "Összeadás eredménye: 4\n", "Összeadás eredménye: -2\n", "Összeadás eredménye: 6\n" ], "name": "stderr" }, { "output_type": "execute_result", "data": { "text/plain": [ "[4, -2, 6]" ] }, "metadata": { "tags": [] }, "execution_count": 9 } ] }, { "cell_type": "markdown", "metadata": { "id": "2LPBdNDnQIdd", "colab_type": "text" }, "source": [ "Dekorátorokat nem csak függvényekre, hanem osztályokra is lehet alkalmazni. Például a rendezési operátorok definícióját megcsinálja nekünk a `functools.total_ordering` dekorátor (csak az egyenlőséget és egy egyenlőtlenséget kell nekünk definiálnunk):" ] }, { "cell_type": "code", "metadata": { "id": "ntBhH4mLQIde", "colab_type": "code", "colab": {}, "outputId": "6958a759-abc8-4695-f029-11b0bb72a281" }, "source": [ "import functools\n", "\n", "@functools.total_ordering\n", "class Results:\n", " def __init__(self, win, loss):\n", " self.win = win\n", " self.loss = loss\n", " def adventage(self):\n", " return self.win-self.loss\n", " def __eq__(self, oth):\n", " \"\"\" operator==() \"\"\"\n", " return self.win == oth.win and self.loss == oth.loss\n", " def __lt__(self, oth):\n", " \"\"\" operator<() \"\"\"\n", " return (self.adventage(), self.win) < (oth.adventage(), oth.win)\n", "\n", "x = Results(6,3)\n", "y = Results(4,2)\n", "z = Results(4,1)\n", "w = Results(3,0)\n", "print(x>=y, x<=z, x!=w, w\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0mp\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mr\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;36m10\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[1;31mAttributeError\u001b[0m: can't set attribute" ] } ] }, { "cell_type": "markdown", "metadata": { "id": "1mY5-m-BQIdo", "colab_type": "text" }, "source": [ "... de definiálhatóak hozzájuk setterek is:" ] }, { "cell_type": "code", "metadata": { "id": "FwDfRMoWQIdp", "colab_type": "code", "colab": {}, "outputId": "81a7e998-5881-4e34-bb09-f7e6207a535c" }, "source": [ "import math\n", "class Point:\n", " def __init__(self, x, y):\n", " self.x = x\n", " self.y = y\n", " @property\n", " def angle(self):\n", " return math.atan2(self.x, self.y)\n", " @angle.setter\n", " def angle(self, value):\n", " r = self.r\n", " self.x = math.cos(value)*r\n", " self.y = math.sin(value)*r\n", " @property\n", " def r(self):\n", " return math.sqrt(self.x**2 + self.y**2)\n", " @r.setter\n", " def r(self, value):\n", " angle = self.angle\n", " self.x = math.cos(angle)*value\n", " self.y = math.sin(angle)*value\n", "\n", "p = Point(3,4)\n", "p.r = 10\n", "print(p.x, p.y)" ], "execution_count": 0, "outputs": [ { "output_type": "stream", "text": [ "8.0 6.0\n" ], "name": "stdout" } ] }, { "cell_type": "markdown", "metadata": { "id": "yHpH-CGcQIds", "colab_type": "text" }, "source": [ "---\n", "\n", "Ahogyan látható, bármilyen számításokat elrejthetünk a property mögött, ennek persze az az ára, hogy a Python rendszer nem tudja és nem akarja ellenőrizni azt, hogy a property valóban kulturált adattagként viselkedik-e (például ha beleírunk egy értéket, akkor utána ugyanaz az érték lesz-e kiolvasható).\n", "\n", "\n", "A propertyk létezésének nagy előnye, hogy nekik köszönhetően Pythonban egy osztály „publikus” interfészében nyugodtan lehetnek publikus adattagok (viszont nem illik `get_foobar()` / `set_foobar()` jellegű gettereket és settereket írni).\n", "\n", "Ha egy adattaghoz később extra funkcionalitást akarunk csatolni (például egy beállítás-fájlból akarjuk kiolvasni vagy ellenőrizni akarjuk, hogy csak megfelelő értéket lehessen beleírni stb.), akkor bármikor lecserélhetjük egy property-re. (Az adattagok többségénél viszont ez sohasem fog bekövetkezni és azoknál élvezhetjük, hogy nem hígítják fel getter-setter metódusok a kódunkat.)\n", "\n", "\n", "---" ] }, { "cell_type": "markdown", "metadata": { "id": "bVEISSEJQIdt", "colab_type": "text" }, "source": [ "### Property-k megvalósításának részletei\n", "\n", "A `property` típus (aminek a konstruktorát az előbb dekorátorként használtuk) nem egyedülálló – hasonló működésre képes bármilyen más típus, ami megvalósítja a megfelelő dupla-aláhúzásjeles metódusokat. (Ugyanúgy, mint ahogy rendezési operátorok alkalmazhatóak minden objektumra, ami `__le__`, `__lt__`, stb. metódusokkal rendelkezik és függvényekhez hasonlóan meghívható minden objektum, ami `__call__` metódust definiál.)\n", "\n", "Figyeljük meg az előző példákban, hogy a property objektumok egy osztálynak az adattagjai voltak (C++ szóhasználattal `static member`-ek) és olyankor viselkedtek furcsán, amikor az adattag-elérés operátor (pont operátor) segítségével „piszkáltuk” őket.\n", "\n", "Összesen négy metódus felel ezért a viselkedésért, de ezek közül csak kettő igazán fontos: a `__get__` és `__set__` metódusok. (A másik kettő a `__delete__` és a `__set_name__` metódus, ezek leírása megtalálható [dokumentációban](https://docs.python.org/3/reference/datamodel.html#implementing-descriptors).)" ] }, { "cell_type": "markdown", "metadata": { "id": "X5Oo5xiTQIdu", "colab_type": "text" }, "source": [ "#### Adattagok olvasása: a `__get__` metódus\n", "Egy $P$ objektumnak a `__get__` metódusa akkor kaphat szerepet, ha valamikor az adattag-elérés (pont operátor) a $P$ objektumot adná vissza eredményül. Ilyenkor meghívódik a $P$​`.__get__` metódus és a pont operátor ennek a metódushívásnak az eredményét fogja visszaadni (ha $P$-nek nem lett volna `__get__` metódusa, akkor maga $P$ lett volna az eredmény).\n", "\n", "Hasonlat a folyamat illusztrálására:\n", "> * Van raktároknak egy hosszú sora (az adattagok).\n", "> * Legtöbb raktárban ládák vannak (`__get__` metódus nélküli objektumok), de van néhány raktár, ahol ehelyett kigyúrt hordárok várakoznak (`__get__` metódussal rendelkező objektumok).\n", "> * Jön egy teherautó egy bizonyos raktárhoz a sorból (adattag-elérés operátor); a sofőr bekiabál, hogy „Hozzátok az árukat!” (megpróbálja meghívni a `__get__` metódust).\n", "> * Ha hordárok voltak a raktárban, akkor azok kijönnek, összeszedik innen-onnan a cuccot és bepakolják a teherautóba, majd tovább várakoznak a raktárban (az eredmény az adattag `__get__` metódusának a visszatérési értéke). \n", "> * Ellenkező esetben a sofőr kénytelen-kelletlen kiszáll és a raktárban lévő dolgokat bepakolja a teherautóba (az eredmény maga az adattag).\n", "\n", "Fontos korlátozás: Ahogyan fentebb is említettük, csak az osztályok adattagjaként (C++ szóhasználattal nagyjából: _static member_) tárolt objektumoknak a `__get__` metódusai kaphatnak szerepet; nem-osztály objektum adattagjaként (C++ szóhasználattal nagyjából: _instance member_) tárolt objektum `__get__` metódusai nem kaphatnak szerepet.\n", "\n", "A `__get__` metódust mindig (egy+)kettő paraméterrel hívja meg a rendszer:\n", "* (`self`, a property-objektum)\n", "* az a példány, aminek az adattagját kérjük _illetve_ `None`, amikor közvetlen osztálytól kérjük az adattagot\n", "* az érintett osztály (aminek a példányának az adattagját _illetve_ aminek az adattagját lekérjük)\n", "\n", "A következő példa bemutatja, hogy milyen helyzetben mik ezek a paraméterek:" ] }, { "cell_type": "code", "metadata": { "scrolled": true, "id": "bviDzEgJQIdv", "colab_type": "code", "colab": {}, "outputId": "c216d6ee-332f-48b2-ee82-74f902741cbb" }, "source": [ "class Prop:\n", " def __init__(self, content):\n", " self.content = content\n", " def __get__(self, inst, cls):\n", " print(\"__get__ meghívva, paraméterek: inst =\", inst, \"cls =\", cls)\n", " return self.content\n", " def __str__(self):\n", " return \"Prop object containing \"+str(self.content)\n", " \n", "class Base:\n", " p1 = Prop(\"one\")\n", "class Deriv(Base):\n", " p2 = Prop(\"two\")\n", " def __init__(self):\n", " self.p3 = Prop(\"three\")\n", "\n", "#adattag-elérés osztályon keresztül:\n", "print(Base.p1)\n", "print(Deriv.p1)\n", "print(Deriv.p2)\n", "#adattag-elérés példányokon keresztül:\n", "b = Base()\n", "print(b.p1)\n", "d = Deriv()\n", "print(d.p1)\n", "print(d.p2)\n", "print(d.p3) #ez nem hívja meg a __get__ metódust, mert p3 nem egy osztálynak az adattagja" ], "execution_count": 0, "outputs": [ { "output_type": "stream", "text": [ "__get__ meghívva, paraméterek: inst = None cls = \n", "one\n", "__get__ meghívva, paraméterek: inst = None cls = \n", "one\n", "__get__ meghívva, paraméterek: inst = None cls = \n", "two\n", "__get__ meghívva, paraméterek: inst = <__main__.Base object at 0x7f9628231d68> cls = \n", "one\n", "__get__ meghívva, paraméterek: inst = <__main__.Deriv object at 0x7f9628231780> cls = \n", "one\n", "__get__ meghívva, paraméterek: inst = <__main__.Deriv object at 0x7f9628231780> cls = \n", "two\n", "Prop object containing three\n" ], "name": "stdout" } ] }, { "cell_type": "markdown", "metadata": { "id": "i-sKk95nQIdy", "colab_type": "text" }, "source": [ "#### Adattagok írása: a `__set__` metódus\n", "\n", "Ha egy `__set__` metódussal rendelkező $P$ objektum egy $C$ osztály adattagjaként (C++ szóhasználattal nagyjából: _static member_) van tárolva, akkor az „ügyintézőként” őrzi azt az adattag-nevet és intézkedik, ha a $C$ osztály egy $I$ példányának olyan nevű adattagjához rendel értéket egy utasítás.\n", "\n", "Az `objektum.adattag = valami` értékadás normális esetben az `objektum` saját adattagjai között tárolná el a `valami` értéket (az `adattag` név alatt), de ez előtt megnézi, hogy `objektum` típusa tárol-e `adattag` név alatt egy `__set__` metódussal rendelkező „ügyintézőt”. Ha van ilyen ügyintéző objektum, akkor (a normális ügymenet helyett) meghívódik annak a `__set__` metódusa, ami ott tárolja el a kapott értéket, ahol akarja.\n", "\n", "A `__set__` metódus (egy+)kettő paraméterrel hívódik meg:\n", "* (`self`, a property-objektum)\n", "* az az objektum, akinek az adattagjához értéket akarunk rendelni\n", "* az érték" ] }, { "cell_type": "markdown", "metadata": { "id": "Pxw-5ngRQIdy", "colab_type": "text" }, "source": [ "A következő kód csinál egy property-típust, ami típustesztelő adattagokat valósít meg ezt a két speciális metódust és a `__set_name__` metódust használva (ami akkor hívódik meg, amikor az objektum egy osztálynak a statikus adattagjává válik valamilyen név alatt):" ] }, { "cell_type": "code", "metadata": { "id": "g_Nl-4LKQIdz", "colab_type": "code", "colab": {}, "outputId": "311d6ac0-e608-4f24-dcab-bd2392a59cd4" }, "source": [ "class IntMember:\n", " def __set_name__(self, cls, name):\n", " self.name = name\n", " @property\n", " def stored_as(self):\n", " return \"_\"+self.name\n", " def __get__(self, inst, cls):\n", " if inst is None:\n", " return self\n", " return getattr(inst, self.stored_as)\n", " def __set__(self, inst, value):\n", " if not isinstance(value, int):\n", " raise TypeError('the value of {} must be an integer'.format(self.name))\n", " setattr(inst, self.stored_as, value)\n", "\n", "class Point:\n", " x = IntMember()\n", " y = IntMember()\n", " def __init__(self, x, y):\n", " self.x = x\n", " self.y = y\n", " def __str__(self):\n", " return '({}, {})'.format(self.x, self.y)\n", " \n", "p = Point(4,5)\n", "print(p)\n", "p.y = 8\n", "print(\"p.y módosítása után:\", p)\n", "print(\"Az x koordináta valójában p._x-ben van tárolva, értéke = \", p._x)\n", "p.x = \"ez nem szám\" # hibát okoz" ], "execution_count": 0, "outputs": [ { "output_type": "stream", "text": [ "(4, 5)\n", "p.y módosítása után: (4, 8)\n", "Az x koordináta valójában p._x-ben van tárolva, értéke = 4\n" ], "name": "stdout" }, { "output_type": "error", "ename": "TypeError", "evalue": "the value of x must be an integer", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 28\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"p.y módosítása után:\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mp\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 29\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Az x koordináta valójában p._x-ben van tárolva, értéke = \"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_x\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 30\u001b[0;31m \u001b[0mp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mx\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m\"ez nem szám\"\u001b[0m \u001b[0;31m# hibát okoz\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;32m\u001b[0m in \u001b[0;36m__set__\u001b[0;34m(self, inst, value)\u001b[0m\n\u001b[1;32m 11\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m__set__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0minst\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mvalue\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 12\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0misinstance\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mvalue\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mint\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 13\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mTypeError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'the value of {} must be an integer'\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mformat\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mname\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 14\u001b[0m \u001b[0msetattr\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0minst\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mstored_as\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mvalue\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 15\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;31mTypeError\u001b[0m: the value of x must be an integer" ] } ] }, { "cell_type": "markdown", "metadata": { "id": "_FkVAVHqQId2", "colab_type": "text" }, "source": [ "### Metódusok\n", "\n", "Ahogyan eddig is folyamatosan használtam, Python-ban az osztályoknak vannak metódusaik, amelyeket az osztály törzsén belül kell definiálni, ugyanazzal a `def` kulcsszóval, ami a hagyományos függvényeket is definiálja.\n", "\n", "Valójában az a helyzet, hogy a függvény típus rendelkezik `__get__` metódussal és emiatt ha egy osztálynak van egy függvény típusú adattagja és azt egy példányon keresztül érjük el, akkor a `__get__` metódus „beilleszti” a példányt a függvény első paraméterének.\n", "\n", "Például:" ] }, { "cell_type": "code", "metadata": { "id": "9mphBd4lQId3", "colab_type": "code", "colab": {}, "outputId": "404a7e07-7931-4d95-aa87-b96fe6f43269" }, "source": [ "class C:\n", " def f(self):\n", " return 42\n", "\n", "print(\"Osztályból elérve:\", type(C.f), C.f(\"akármi\"))\n", " # C.f egy függvény – bármit megadhatunk első paraméterként\n", "print(\"Ugyanez máshogy:\", type(C.f.__get__(None, C)), C.f.__get__(None, C)(\"akármi\"))\n", "\n", "c = C()\n", "print(\"Példányból elérve:\", type(c.f), c.f())\n", "print(\"Ugyanez máshogy:\", type(C.f.__get__(c, C)), C.f.__get__(c, C)())\n", "\n", "def make_pair(x, y):\n", " \"Ez egy teljesen közönséges függvény...\"\n", " return (x, y)\n", "\n", "C.make_pair = make_pair #... amit eltárolunk az osztály adattagjaként\n", "\n", "print(\"... és meghívunk metódusként:\", c.make_pair(\"spam spam spam\"))" ], "execution_count": 0, "outputs": [ { "output_type": "stream", "text": [ "Osztályból elérve: 42\n", "Ugyanez máshogy: 42\n", "Példányból elérve: 42\n", "Ugyanez máshogy: 42\n", "... és meghívunk metódusként: (<__main__.C object at 0x7f96281b22b0>, 'spam spam spam')\n" ], "name": "stdout" } ] }, { "cell_type": "markdown", "metadata": { "id": "zXz4SjtrQId5", "colab_type": "text" }, "source": [ "A függvényeknek ezt a viselkedését könnyen utánozhatnánk, ha akarnánk:" ] }, { "cell_type": "code", "metadata": { "scrolled": true, "id": "GWqCTDhLQId6", "colab_type": "code", "colab": {}, "outputId": "792aaba2-7873-42a9-9927-c8ede7586673" }, "source": [ "from functools import partial\n", "\n", "class MyMakePair:\n", " \"\"\"A függvény típust _alaposan_ utánzó saját típus.\n", " A függvényhívás operátor mellett a __get__ viselkedését\n", " is utánozza.\"\"\"\n", " def __call__(self, x, y):\n", " return (x, y)\n", " def __get__(self, inst, cls):\n", " if inst:\n", " return partial(self, inst)\n", " # inst beszúrása első paraméterként\n", " else:\n", " return self\n", "\n", "class SomeClass:\n", " custom_method = MyMakePair()\n", "\n", "obj = SomeClass()\n", "print(type(obj.custom_method))\n", "print(obj.custom_method(\"spam spam spam\"))" ], "execution_count": 0, "outputs": [ { "output_type": "stream", "text": [ "\n", "(<__main__.SomeClass object at 0x7f96281ae438>, 'spam spam spam')\n" ], "name": "stdout" } ] }, { "cell_type": "markdown", "metadata": { "id": "ZVJxoLPoQId8", "colab_type": "text" }, "source": [ "### Statikus és osztályhoz kötődő metódusok\n", "\n", "Pythonban is vannak statikus metódusok, ezeket – micsoda meglepetés – egy dekorátor segítségével vezetjük be:" ] }, { "cell_type": "code", "metadata": { "id": "OHcrPc43QId9", "colab_type": "code", "colab": {}, "outputId": "0f126ebe-53ad-4cb4-e30f-6747afa2c25b" }, "source": [ "import math\n", "class Point:\n", " def __init__(self, x, y):\n", " self.x = x\n", " self.y = y\n", " @staticmethod\n", " def polar(r, angle):\n", " return Point(r*math.cos(angle), r*math.sin(angle))\n", " def __str__(self):\n", " return f'({self.x}, {self.y})'\n", "\n", "print(Point.polar(3, math.pi))\n", "p = Point(5,3)\n", "print(\"Also callable from instances:\", p.polar(2,math.pi/4))\n", "print(\"The real object:\", Point.__dict__[\"polar\"])\n", "print(\"Getting from the class:\", Point.polar)\n", "print(\"Getting from an instance:\", p.polar)" ], "execution_count": 0, "outputs": [ { "output_type": "stream", "text": [ "(-3.0, 3.6739403974420594e-16)\n", "Also callable from instances: (1.4142135623730951, 1.414213562373095)\n", "The real object: \n", "Getting from the class: \n", "Getting from an instance: \n" ], "name": "stdout" } ] }, { "cell_type": "markdown", "metadata": { "id": "DLWXiztnQId_", "colab_type": "text" }, "source": [ "A `staticmethod` típus C-ben van definiálva (és azonnal elérhető, nem kell hozzá importálni semmit), de Python-ban is könnyen megírható lenne:" ] }, { "cell_type": "code", "metadata": { "scrolled": true, "id": "sVSCrbEIQIeA", "colab_type": "code", "colab": {}, "outputId": "9c53e299-6553-417c-ed13-7fcde9967ec6" }, "source": [ "class my_staticmethod:\n", " def __init__(self, func):\n", " self.func = func\n", " def __get__(self, inst, cls):\n", " return self.func\n", "\n", "import math\n", "class Point:\n", " def __init__(self, x, y):\n", " self.x = x\n", " self.y = y\n", " @my_staticmethod\n", " def polar(r, angle):\n", " return Point(r*math.cos(angle), r*math.sin(angle))\n", " def __str__(self):\n", " return f'({self.x}, {self.y})'\n", "\n", "print(Point.polar(3, math.pi))\n", "p = Point(5,3)\n", "print(\"Also callable from instances:\", p.polar(2,math.pi/4))\n", "print(\"The real object:\", Point.__dict__[\"polar\"])\n", "print(\"Getting from the class:\", Point.polar)\n", "print(\"Getting from an instance:\", p.polar)" ], "execution_count": 0, "outputs": [ { "output_type": "stream", "text": [ "(-3.0, 3.6739403974420594e-16)\n", "Also callable from instances: (1.4142135623730951, 1.414213562373095)\n", "The real object: <__main__.my_staticmethod object at 0x7f96281aee80>\n", "Getting from the class: \n", "Getting from an instance: \n" ], "name": "stdout" } ] }, { "cell_type": "markdown", "metadata": { "id": "BbfRt3DsQIeC", "colab_type": "text" }, "source": [ "Megjegyzés: az `__str__` függvényben egy [formázott sztringliterált](https://docs.python.org/3/reference/lexical_analysis.html#formatted-string-literals) lett alkalmazva. Ez egy frissen (Python 3.6-ban) bevezetett nyelvi elem, amivel tömören lehet leírni sztringek összebarkácsolását." ] }, { "cell_type": "markdown", "metadata": { "id": "zFUqKkpzQIeD", "colab_type": "text" }, "source": [ "Python-ban van még egy metódusfajta, a `classmethod`, ami az aktuális osztályt kapja meg paraméterként. Ennek a `staticmethod`-hoz hasonló szerepe van, azonban „jól viselkedik” öröklődés során.\n", "\n", "Egy olyan osztály, aminek csak statikus adattagjai és `classmethod`-jai vannak, gyakorlatilag úgy viselkedik, mintha egy szingleton objektum lenne.\n", "\n", "Az előző példaprogramban a `staticmethod`-ot `classmethod`-ra cserélve a következő lesz a program:" ] }, { "cell_type": "code", "metadata": { "id": "-7Tu9LoeQIeE", "colab_type": "code", "colab": {}, "outputId": "a65bdd3c-346e-4037-a07e-0304a0641c4c" }, "source": [ "import math\n", "class Point:\n", " X = 42\n", " def __init__(self, x, y):\n", " self.x = x\n", " self.y = y\n", " @classmethod\n", " def polar(cls, r, angle):\n", " print(cls.X)\n", " return cls(r*math.cos(angle), r*math.sin(angle))\n", " def __str__(self):\n", " return f'({self.x}, {self.y})'\n", "\n", "class BarePoint(Point):\n", " X = 67\n", " def __str__(self):\n", " return f'{self.x} {self.y}'\n", "\n", "print(Point.polar(3, math.pi))\n", "print(BarePoint.polar(3, math.pi))\n", "p = BarePoint(5,3)\n", "print(\"Also callable from instances:\", p.polar(2,math.pi/4))\n", "print(\"The real object:\", Point.__dict__[\"polar\"])\n", "print(\"Getting from the class:\", Point.polar)\n", "print(\"Getting from another class:\", BarePoint.polar)\n", "print(\"Getting from an instance:\", p.polar)" ], "execution_count": 0, "outputs": [ { "output_type": "stream", "text": [ "42\n", "(-3.0, 3.6739403974420594e-16)\n", "67\n", "-3.0 3.6739403974420594e-16\n", "67\n", "Also callable from instances: 1.4142135623730951 1.414213562373095\n", "The real object: \n", "Getting from the class: >\n", "Getting from another class: >\n", "Getting from an instance: >\n" ], "name": "stdout" } ] }, { "cell_type": "markdown", "metadata": { "id": "NXAZ-GAGQIeG", "colab_type": "text" }, "source": [ "---\n", "\n", "### Másik példát is nézzünk:\n", " \n", "---" ] }, { "cell_type": "code", "metadata": { "id": "brNTE98wQIeH", "colab_type": "code", "colab": {}, "outputId": "56ad96b1-e476-4dc1-fc25-1e7cbaa1ffe3" }, "source": [ "# Az object osztály egy alosztályát képezzük\n", "class Human(object):\n", " # Osztály szintű mező: az osztály összes példányában azonos\n", " species = \"Ember\"\n", "\n", " # Ez a függvény meghívódik az osztály példányosításakor.\n", " def __init__(self, name):\n", " # A paramétert értékül adjuk a példány name attribútumának\n", " self.name = name\n", " # Inicializálunk egy mezőt\n", " self.age = 0\n", "\n", " # Példány metódus. Minden metódus első paramétere a \"self\", a példány maga\n", " def say(self, msg):\n", " return \"{0} mondja : {1}\".format(self.name, msg)\n", " \n", " # Egy osztálymetódus az osztály összes példány közt meg van osztva.\n", " # Hívásukkor az első paraméter mindig a hívó osztály (cls).\n", " @classmethod\n", " def get_species(cls):\n", " return cls.species\n", "\n", " # Egy statikus metódus osztály és példányreferencia nélkül hívódik\n", " @staticmethod\n", " def gurgulazo():\n", " return \"Ha ha haaaa*\"\n", "\n", " # Egy property jelölésű függvény olyan, mint egy getter.\n", " # Használatával az age mező egy csak-olvasható attribútummá válik.\n", " @property\n", " def age(self):\n", " return self._age\n", " \n", "\n", " # Így lehet settert megadni egy mezőhöz\n", " @age.setter\n", " def age(self, age):\n", " self._age = age\n", "\n", " # Így lehet egy mező törlését engedélyezni\n", " @age.deleter\n", " def age(self):\n", " del self._age\n", "\n", "i = Human(name=\"Lajos\")\n", "print (i.say(\"Üdvözöllek\") )\n", "\n", "# Hívjuk az osztály metódusunkat\n", "print(i.get_species())\n", "\n", "# Változtassuk meg az osztály szintű attribútumot\n", "Human.species = \"Gondolkodó ember\"\n", "\n", "# Hívjuk az osztály metódusunkat\n", "print(i.get_species())\n", "\n", "# metodus meghívása\n", "print(Human.gurgulazo())\n", "\n", "##" ], "execution_count": 0, "outputs": [ { "output_type": "stream", "text": [ "Lajos mondja : Üdvözöllek\n", "Ember\n", "Gondolkodó ember\n", "Ha ha haaaa*\n" ], "name": "stdout" } ] } ] }