{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "Decoratori\n", "==========\n", "\n", "La lezione è basata su https://realpython.com/primer-on-python-decorators/.\n", "\n", "Un decoratore è una funzione che prende come argomento un'altra funzione e ne amplia il comportamento senza modificarla eplicitamente. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In Python le funzioni sono **Oggetti di Prima Classe** cioè possono essere passate come argomenti ad altre funzioni come qualunque altro oggetto (int, float, list etc.)" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "def say_hello(name):\n", " return f\"Hello {name}\"\n", "\n", "\n", "def be_awesome(name):\n", " return f\"Yo {name}, together we are the awesomest!\"\n", "\n", "\n", "def greet_bob(greeter_func, name =\"Bob\"):\n", " return greeter_func(name)" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'Hello Bob'" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "greet_bob(say_hello) # function name as argument" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'Yo Bob, together we are the awesomest!'" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "greet_bob(be_awesome)" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'Hello Peter'" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "greet_bob(say_hello,\"Peter\") # function name and its input as arguments" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Possiamo definire funzioni all'interno di funzioni e una funzione può restituire funzioni:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "def parent(num):\n", " def first_child():\n", " return \"Hi, I am Emma\"\n", "\n", " def second_child():\n", " return \"Call me Liam\"\n", "\n", " if num == 1:\n", " return first_child\n", " else:\n", " return second_child" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`parent` restituisce una referenza (indirizzo di memoria) a una funzione (Si noti che nel `return` il nome della funzione è privo di parentesi tonde. Provate e restituire `first_child()` ), che possiamo poi invocare normalmente:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/plain": [ ".first_child()>" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "first = parent(1)\n", "first" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'Hi, I am Emma'" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "first()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Un primo esempio di decoratore" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "def my_decorator(func): # decorator on a generic function with no input\n", " def wrapper():\n", " print(\"Something is happening before the function is called.\")\n", " func()\n", " print(\"Something is happening after the function is called.\")\n", "\n", " return wrapper # return reference to an internal function. `wrapper` non può essere invocato dall'esterno di `my_decorator`" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "ename": "NameError", "evalue": "name 'wrapper' is not defined", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mwrapper\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mNameError\u001b[0m: name 'wrapper' is not defined" ] } ], "source": [ "wrapper()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Definiamo una funzione:" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "def say_whee():\n", " print(\"Whee!\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Definiamo una versione decorata di `say_whee`:" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [], "source": [ "decorated_say_whee = my_decorator(say_whee) # a wrapper viene pasata la referenza alla funzione `say_whee`" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Something is happening before the function is called.\n", "Whee!\n", "Something is happening after the function is called.\n" ] } ], "source": [ "decorated_say_whee() # decorated_say_whee viene effettivamente eseguita. `func` in wrapper ora punta a `say_whee`." ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", ".wrapper at 0x107fb8940>\n" ] } ], "source": [ "print(say_whee)\n", "print(decorated_say_whee)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Possiamo anche ridefinire `say_whee` come la versione decorata di se stessa:" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [], "source": [ "say_whee = my_decorator(say_whee)" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Something is happening before the function is called.\n", "Whee!\n", "Something is happening after the function is called.\n" ] } ], "source": [ "say_whee()" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ ".wrapper at 0x107fb8d30>\n" ] } ], "source": [ "print(say_whee)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Riassumendo: un decoratore si `avvolge` attorno ad una funzione e ne modifica il comportamento.\n", "\n", "### Esempio 2\n", "\n", "L'azione svolta da un decoratore può essere determinato dinamicamente, cioè quando la funzione decorata viene chiamata" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [], "source": [ "from datetime import datetime\n", "\n", "def not_during_the_night(func):\n", " def wrapper():\n", " if 7 <= datetime.now().hour < 22:\n", " func()\n", " else:\n", " pass # Hush, the neighbors are asleep.\n", "\n", " return wrapper" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [], "source": [ "def honk():\n", " print(\"Honk! Honk!\")" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [], "source": [ "honk = not_during_the_night(honk)" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Honk! Honk!\n" ] } ], "source": [ "honk()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Se chiamate `honk` fra le 22 e le 7 non succede nulla.\n", "\n", "Esiste un modo sintetico di ridefinire una funzione come la funzione stessa inglobata in un decoratore, usando il simbolo `@`:" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [], "source": [ "def my_decorator(func):\n", " def wrapper():\n", " print(\"Something is happening before the function is called.\")\n", " func()\n", " print(\"Something is happening after the function is called.\")\n", "\n", " return wrapper\n", "\n", "@my_decorator\n", "def say_whee():\n", " print(\"Whee!\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`@my_decorator` prima della definizione di `say_whee` è equivalente a definire say_whee e poi aggiugere la linea \n", "`say_whee = my_decorator(say_whee)`." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Riutilizzare i decoratori\n", "\n", "Se i decoratori vengono salvati in un file possono essere importati come normali funzioni e utilizzati in ambiti diversi.\n", "\n", "Esempio:\n", "\n", "Supponiamo che in decoratori.py ci sia il codice che segue:\n", "```\n", "def do_twice(func):\n", " def wrapper_do_twice():\n", " func()\n", " func()\n", " return wrapper_do_twice\n", "```" ] }, { "cell_type": "code", "execution_count": 56, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "/Users/maina/cernbox/python/MyCourse2/ShellPrograms\n", "/Users/maina/cernbox/python/MyCourse2/ipynb\n" ] } ], "source": [ "%cd ../ShellPrograms/\n", "from decorators import do_twice\n", "%cd ../ipynb\n", "\n", "@do_twice # declare the decorated function\n", "def say_whee_twice():\n", " print(\"Whee!\")" ] }, { "cell_type": "code", "execution_count": 57, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Whee!\n", "Whee!\n" ] } ], "source": [ "say_whee_twice() # call the decorated function" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Decorare funzioni con argomenti\n", "Per decorare funzioni con input arbitrari è necessario utilizzare la notazione `*args, **kwargs`. L'input è passato al wrapper interno che a sua volta lo passa alle funzioni da decorare." ] }, { "cell_type": "code", "execution_count": 58, "metadata": {}, "outputs": [], "source": [ "def do_twice(func):\n", " \"\"\"Run the decorated function twice\"\"\"\n", " def wrapper_do_twice(*args, **kwargs):\n", " func(*args, **kwargs)\n", " func(*args, **kwargs)\n", " return wrapper_do_twice" ] }, { "cell_type": "code", "execution_count": 59, "metadata": {}, "outputs": [], "source": [ "@do_twice\n", "def greet(name):\n", " print(f\"Hello {name}\")" ] }, { "cell_type": "code", "execution_count": 60, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Hello Pippo\n", "Hello Pippo\n" ] } ], "source": [ "greet(\"Pippo\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Ritornare valori da una funzione decorata\n", "Nell'esempio seguente l'introduzione del decoratore functools.wraps serve a facilitare l'accesso alla funzione più interna" ] }, { "cell_type": "code", "execution_count": 61, "metadata": {}, "outputs": [], "source": [ "import functools\n", "\n", "def do_twice(func):\n", " \"\"\"Run the decorated function twice\"\"\"\n", " @functools.wraps(func)\n", " def wrapper_do_twice(*args, **kwargs):\n", " func(*args, **kwargs)\n", " return func(*args, **kwargs)\n", " return wrapper_do_twice" ] }, { "cell_type": "code", "execution_count": 62, "metadata": {}, "outputs": [], "source": [ "@do_twice\n", "def return_greeting(name):\n", " print(\"Creating greeting\")\n", " return f\"Hi {name}\"" ] }, { "cell_type": "code", "execution_count": 63, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Creating greeting\n", "Creating greeting\n" ] }, { "data": { "text/plain": [ "'Hi Paperino'" ] }, "execution_count": 63, "metadata": {}, "output_type": "execute_result" } ], "source": [ "return_greeting(\"Paperino\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Verifichiamo che la funzione decorata ritorni effettivamente il risultato:" ] }, { "cell_type": "code", "execution_count": 64, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Creating greeting\n", "Creating greeting\n", "La funzione ha restituito: Hi Paperino\n" ] } ], "source": [ "print(\"La funzione ha restituito:\",return_greeting(\"Paperino\"))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Decoratori nella definizione di classi\n", "\n", "Python possiede dei decoratori built-in che vengono comunemente utilizzati nella costruzione di classi, @classmethod, @staticmethod, and @property. I decoratori @classmethod and @staticmethod vengono usati per definire metodi interni alla classe che non sono collegati alle particolari istanze della classe stessa. Il decoratore @property definisce un attributo (può essere richiamato senza le parentesi () ). In mancanza di un metodo `setter` l'attributo non può essere modificato dall'esterno." ] }, { "cell_type": "code", "execution_count": 73, "metadata": {}, "outputs": [], "source": [ "# I `@classmethods` sono essenzialmente degli inizializzatori più specializzati\n", "# Notare che @classmethods ha come primo argomento il nome della classe e che viene ereditato dalle sottoclassi\n", "# L'underscore in self._radius = radius vuol dire che radius è un attributo PRIVATO\n", "class Circle:\n", " \n", " def __init__(self, radius):\n", " self._radius = radius\n", "\n", " @property\n", " def radius(self):\n", " \"\"\"Get value of radius\"\"\"\n", " return self._radius\n", "\n", " @radius.setter\n", " def radius(self, value):\n", " \"\"\"Set radius, raise error if negative\"\"\"\n", " if value >= 0:\n", " self._radius = value\n", " else:\n", " raise ValueError(\"Radius must be positive\")\n", "\n", " @property\n", " def area(self): # area non ha setter\n", " \"\"\"Calculate area inside circle\"\"\"\n", " return self.pi() * self.radius**2\n", "\n", " def cylinder_volume(self, height):\n", " \"\"\"Calculate volume of cylinder with circle as base\"\"\"\n", " return self.area * height\n", "\n", " @classmethod\n", " def unit_circle(cls):\n", " \"\"\"Factory method creating a circle with radius 1\"\"\"\n", " return cls(1)\n", "\n", " @staticmethod\n", " def pi():\n", " \"\"\"Value of π (could use math.pi instead though, if math had been imported)\"\"\"\n", " return 3.1415926536" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Quache esempio" ] }, { "cell_type": "code", "execution_count": 66, "metadata": {}, "outputs": [], "source": [ "C1 = Circle(3)" ] }, { "cell_type": "code", "execution_count": 67, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "3" ] }, "execution_count": 67, "metadata": {}, "output_type": "execute_result" } ], "source": [ "C1.radius" ] }, { "cell_type": "code", "execution_count": 68, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "28.2743338824" ] }, "execution_count": 68, "metadata": {}, "output_type": "execute_result" } ], "source": [ "C1.area" ] }, { "cell_type": "code", "execution_count": 69, "metadata": {}, "outputs": [], "source": [ "C1.radius = 3.4" ] }, { "cell_type": "code", "execution_count": 70, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "36.316811075615995" ] }, "execution_count": 70, "metadata": {}, "output_type": "execute_result" } ], "source": [ "C1.area" ] }, { "cell_type": "code", "execution_count": 71, "metadata": {}, "outputs": [ { "ename": "AttributeError", "evalue": "can't set attribute", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mC1\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0marea\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;36m37.9\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mAttributeError\u001b[0m: can't set attribute" ] } ], "source": [ "C1.area = 37.9" ] }, { "cell_type": "code", "execution_count": 76, "metadata": {}, "outputs": [], "source": [ "C2 = Circle(-1)" ] }, { "cell_type": "code", "execution_count": 77, "metadata": {}, "outputs": [ { "ename": "ValueError", "evalue": "Radius must be positive", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mC2\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mradius\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m-\u001b[0m\u001b[0;36m2\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;32m\u001b[0m in \u001b[0;36mradius\u001b[0;34m(self, value)\u001b[0m\n\u001b[1;32m 20\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_radius\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 21\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 22\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mValueError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Radius must be positive\"\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 23\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 24\u001b[0m \u001b[0;34m@\u001b[0m\u001b[0mproperty\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;31mValueError\u001b[0m: Radius must be positive" ] } ], "source": [ "C2.radius = -2" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Decoratori, sottoclassi e inheritance\n", "\n", "Una sottoclasse di Circle:" ] }, { "cell_type": "code", "execution_count": 104, "metadata": {}, "outputs": [], "source": [ "class Cone(Circle):\n", " def __init__(self, radius, height):\n", " Circle.__init__(self, radius) # La parte di inizializzazione che riguarda `radius` viene eriditata da `Circle`\n", " self._height = height\n", " \n", "# La definizione dell'attributo radius e il setter corrispondente vengono eriditata da `Circle`\n", "\n", " @property # Nuovo attributo specifico della sottoclasse\n", " def height(self):\n", " \"\"\"Get value of height\"\"\"\n", " return self._height\n", "\n", " @height.setter\n", " def height(self, value):\n", " \"\"\"Set radius, raise error if negative\"\"\"\n", " if value >= 0:\n", " self._height = value\n", " else:\n", " raise ValueError(\"height must be positive\")\n", "\n", " def cone_volume(self): # Il metodo utilizza un metodo della classe genitore\n", " \"\"\"Calculate volume of cone with circle as base\"\"\"\n", " return self.cylinder_volume(self.height)/3" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Qualche esempio" ] }, { "cell_type": "code", "execution_count": 99, "metadata": {}, "outputs": [], "source": [ "cone1 = Cone(-1,3)" ] }, { "cell_type": "code", "execution_count": 100, "metadata": {}, "outputs": [ { "ename": "ValueError", "evalue": "Radius must be positive", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mcone1\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mradius\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m-\u001b[0m\u001b[0;36m2\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;32m\u001b[0m in \u001b[0;36mradius\u001b[0;34m(self, value)\u001b[0m\n\u001b[1;32m 20\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_radius\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 21\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 22\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mValueError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Radius must be positive\"\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 23\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 24\u001b[0m \u001b[0;34m@\u001b[0m\u001b[0mproperty\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;31mValueError\u001b[0m: Radius must be positive" ] } ], "source": [ "cone1.radius = -2" ] }, { "cell_type": "code", "execution_count": 80, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "3.1415926536" ] }, "execution_count": 80, "metadata": {}, "output_type": "execute_result" } ], "source": [ "cone1.pi()" ] }, { "cell_type": "code", "execution_count": 105, "metadata": {}, "outputs": [], "source": [ "cone2 = Cone(2,3)" ] }, { "cell_type": "code", "execution_count": 106, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "12.5663706144" ] }, "execution_count": 106, "metadata": {}, "output_type": "execute_result" } ], "source": [ "cone2.cone_volume()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Parlare di @dataclass" ] } ], "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.8.5" } }, "nbformat": 4, "nbformat_minor": 4 }