{ "nbformat": 4, "nbformat_minor": 0, "metadata": { "colab": { "provenance": [], "authorship_tag": "ABX9TyNlUlryw5Ej8oUX+bGxwrCp", "include_colab_link": true }, "kernelspec": { "name": "python3", "display_name": "Python 3" }, "language_info": { "name": "python" } }, "cells": [ { "cell_type": "markdown", "metadata": { "id": "view-in-github", "colab_type": "text" }, "source": [ "\"Open" ] }, { "cell_type": "markdown", "source": [ "

⭐PYTHON DECORATORS⭐

\n", "\n", "
" ], "metadata": { "id": "R9p27aTOEL8H" } }, { "cell_type": "markdown", "source": [ "## What are Decorators?\n", "
\n", " Decorators\n", "

\n", " \n", " 1. Decorators are one of the most helpful and powerful tools of Python\n", "\n", " 2. These are used to modify the behavior of the function\n", " \n", " 3. Decorators provide the flexibility to wrap another function to expand the working of wrapped function, without permanently modifying it\n", " \n", " 4. In Decorators, functions are passed as an argument into another function and then called inside the wrapper function\n", "

\n", "

" ], "metadata": { "id": "sJ8N3HXmExHP" } }, { "cell_type": "markdown", "source": [ "## Objects in Python\n", "\n", "Python has the most interesting feature that everything is treated as an obect even classes or any variable we define in Python is also assumed as an object. Functions are first-class objects in Python because they can reference to, passed to a variable and returned from other functions as well!\n", "\n", " \n", "\n" ], "metadata": { "id": "o8vQO6szFtaN" } }, { "cell_type": "markdown", "source": [ "## Examples:" ], "metadata": { "id": "m_dGYJ23HeO6" } }, { "cell_type": "code", "execution_count": null, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "hbIV_ljPEF4B", "outputId": "21da8637-b29f-4c6e-d92e-74332f45a9c6" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Hi\n", "Hi\n" ] } ], "source": [ "def display(msg):\n", " print(msg)\n", "display(\"Hi\")\n", "func2 = display\n", "func2(\"Hi\")" ] }, { "cell_type": "markdown", "source": [ "### A function can be returned from another function\n", "\n", "An inner function in Python can be returned from the outer function" ], "metadata": { "id": "YOnJHdN_Hqfp" } }, { "cell_type": "code", "source": [ "def greet():\n", " def greeting_at_dawn():\n", " print(\"Good morning!\")\n", " return greeting_at_dawn\n", "\n", "salute = greet()\n", "salute()" ], "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "2yjqtCRBHm57", "outputId": "bef4665b-e4a5-4a76-ed06-20040840cfed" }, "execution_count": null, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Good morning!\n" ] } ] }, { "cell_type": "markdown", "source": [ "### A function can be passed as an argument of another function\n", "\n", "A function that receives a function argument is known as a higher order function" ], "metadata": { "id": "ZkzlhzNUIXm7" } }, { "cell_type": "code", "source": [ "def greet_some(func):\n", " print(\"Good morning!\",end=' ')\n", " func()\n", "\n", "def say_name():\n", " print(\"Chetan\")\n", "\n", "greet_some(say_name)" ], "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "-UHzBPKoH7lu", "outputId": "e7f59d54-c740-4959-ce44-e2efa7b42d8a" }, "execution_count": null, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Good morning! Chetan\n" ] } ] }, { "cell_type": "markdown", "source": [ "## How Python Decorators Work?\n", "\n", "A simple decorator function starts with a function definition, the decorator function, and then a nested function within the other wrapper function.\n", "\n", "* Always keep these two main points in mind when defining decorators:\n", " 1. To implment decorators, define an outer fuction that takes a function argument\n", " 2. Nest a wrapper function within the outer decorator function, which also wraps the decorated function\n" ], "metadata": { "id": "7UmKRJDvJN8C" } }, { "cell_type": "code", "source": [ "# example\n", "\n", "#higher order fucntion or decorator,\n", "# accepts func i.e. a method as argument\n", "# returns a method in the return statement\n", "\n", "def increase_number(func):\n", " def increase_by_one(): #wrapper function\n", " print(\"incrementing number by 1 ...\")\n", " #decorated get_number is assigned to another variable\n", " number_plus_one = func()+1\n", " return number_plus_one #new variable is returned\n", " return increase_by_one #wrapper is returned\n", "\n", "# inner function\n", "def get_number():\n", " return 5\n", "\n", "#implementing decorator\n", "get_new_number = increase_number(get_number)\n", "print(get_new_number())" ], "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "wC6T0mDcItzm", "outputId": "3c6592ca-11e9-4654-97cf-a4272dab7fd0" }, "execution_count": null, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "incrementing number by 1 ...\n", "6\n" ] } ] }, { "cell_type": "markdown", "source": [ "## To Avoid Confusion: use @\n", "A simple decorator function is easily identified when it begins with the _@_ prefix, coupled with the decorated function underneath. \n", "We can edit our example to look like this." ], "metadata": { "id": "LeVk7PmoMVSQ" } }, { "cell_type": "code", "source": [ "def increase_number(func):\n", " def increase_by_one():\n", " print(\"incrementing by 1...\")\n", " number_plus_one = func() + 1\n", " return number_plus_one\n", " return increase_by_one\n", "\n", "@increase_number #decorator increase_number\n", "#extends the functionality of get_number()\n", "def get_number():\n", " return 5\n", "\n", "print(get_number())" ], "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "DtmJJp_dKhUK", "outputId": "97723e49-5b79-46e5-d630-7bce1cd739f4" }, "execution_count": null, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "incrementing by 1...\n", "6\n" ] } ] }, { "cell_type": "markdown", "source": [ "## Decorator Functions with Parameters\n", "\n", "There are cases where you may need to pass parameters to a decorator. The way around this is to pass parameters to the wrapper function, which are then passed down to the decorator" ], "metadata": { "id": "rgxVRYXrNsZp" } }, { "cell_type": "code", "source": [ "# Example:\n", "\n", "# decorator definition\n", "def multiply_numbers(func):\n", " #wrapper function\n", " def multiply_two_numbers(num1, num2):\n", " print(f\"we're multiplying two number {num1} and {num2}\")\n", " return func(num1,num2)\n", " return multiply_two_numbers\n", "\n", "# decorator multiply_numbers\n", "@multiply_numbers\n", "# inner function arguments/decorated function arguments\n", "# are passed to wrapper through the decorator\n", "def multiply_two_given_numbers(num1,num2):\n", " return f'{num1} * {num2} = {num1 * num2}'\n", "\n", "print(multiply_two_given_numbers(2,5))" ], "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "RDbFrkwPNjPW", "outputId": "20c362ea-f85b-4e36-cea7-4c0b667446fa" }, "execution_count": null, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "we're multiplying two number 2 and 5\n", "2 * 5 = 10\n" ] } ] }, { "cell_type": "markdown", "source": [ "## Passing ```*args``` and ```**kwargs``` to decorated function\n", "\n", "* Any number of arguments ```(*args)``` or key word arguments ```(**kwargs)``` can be passed unto the decorated function.\n", "* ```*args``` allows the collection of all positional arguments, while ```**kwargs``` is for all the keyword arguments required and needed during the function call \n", "* ```*args``` forms an iterable of positional arguments as a tuple, while the ```**kwargs``` forms a dictionay of keyword arguments" ], "metadata": { "id": "Hwm8wLR6QKHJ" } }, { "cell_type": "code", "source": [ "# Example: \n", "\n", "def decorator_func(decorated_func):\n", " def wrapper_func(*args, **kwargs):\n", " print(f\"there are {len(args)} positional arguments and {len(kwargs)} keyword arguments\")\n", " return decorated_func(*args, **kwargs)\n", " return wrapper_func\n", "\n", "@decorator_func\n", "def names_and_age(age1, age2, name1=\"Ben\", name2=\"Harry\"):\n", " return f\"{name1} is {age1} years old and {name2} is {age2} years old\"\n", "\n", "print(names_and_age(12,15,name1=\"Lily\",name2=\"Kishor\"))" ], "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "RuuaXt_KO5u7", "outputId": "f61610c0-f0f0-40b4-b762-d65fc61d7f0a" }, "execution_count": null, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "there are 2 positional arguments and 2 keyword arguments\n", "Lily is 12 years old and Kishor is 15 years old\n" ] } ] }, { "cell_type": "markdown", "source": [ "## Multiple Decorators or Decorator Chaining in Python\n", "\n", "* A function can be decorated with chain of decorators.\n", "* This is achieved by stacking one decorator on top of another, in no particular order\n", "* You will achieve the same output no matter the order" ], "metadata": { "id": "oVKpHdyfSHBF" } }, { "cell_type": "code", "source": [ "# Example: \n", "\n", "def increase_decorator(func):\n", " def increase_by_two():\n", " print('increase by 2...')\n", " new_number = func()\n", " return new_number + 2\n", " return increase_by_two\n", "\n", "def decrease_decorator(func):\n", " def decrease_by_one():\n", " print('decrease by 1...')\n", " new_number = func()\n", " return new_number -1\n", " return decrease_by_one\n", "\n", "@increase_decorator\n", "@decrease_decorator\n", "def get_number():\n", " return 5\n", "\n", "print(get_number())" ], "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "e1uclpuMSmN3", "outputId": "0ebb603b-d841-4748-c63c-814ec8fbae0d" }, "execution_count": null, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "increase by 2...\n", "decrease by 1...\n", "6\n" ] } ] }, { "cell_type": "markdown", "source": [ "### Example for ``a*x^b`` with ```*args``` and ```**kwargs```" ], "metadata": { "id": "jUhzn_5vlGVp" } }, { "cell_type": "code", "source": [ "# Example for a*x^b\n", "# get_number will be get_number\n", "# a_into_x will be a decorator\n", "# x_raise2_b will be another decorator\n", "\n", "def a_into_x(func):\n", " def mul_a_x(*args, **kwargs):\n", " print('multiply a to x...')\n", " atuple = func(*args, **kwargs)\n", " return atuple[0], atuple[1], atuple[2]*atuple[0]\n", " return mul_a_x\n", "\n", "def x_raise2_b(func):\n", " def pow_x_b(*args, **kwargs):\n", " print('raise power of x by b...')\n", " btuple = func(*args, **kwargs)\n", " return btuple[0], btuple[1], pow(btuple[2], btuple[1])\n", " return pow_x_b\n", "\n", "@a_into_x\n", "@x_raise2_b\n", "def get_number(a=1, b=1, x=None):\n", " if x is None:\n", " x = int(input(\"Give x for a*x^b: \"))\n", " return a, b, x\n", "\n", "print(get_number(a=2, b=3))" ], "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "HVILo1CfipDZ", "outputId": "e95e9db2-f0e6-4e8c-9ed1-4b1f7a79ca38" }, "execution_count": null, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "multiply a to x...\n", "raise power of x by b...\n", "Give x for a*x^b: 4\n", "(2, 3, 128)\n" ] } ] }, { "cell_type": "markdown", "source": [ "### Simpler example without ```*args``` and ```**kwargs```" ], "metadata": { "id": "cTLp2jwflLo8" } }, { "cell_type": "code", "source": [ "def multiply_by_a(func):\n", " def wrapper(a, b, x):\n", " return a * func(a, b, x)\n", " return wrapper\n", "\n", "def raise_to_power_b(func):\n", " def wrapper(a, b, x):\n", " return func(a, b, x) ** b\n", " return wrapper\n", "\n", "@multiply_by_a\n", "@raise_to_power_b\n", "def calculate(a, b, x):\n", " return x\n", "\n", "result = calculate(2, 3, 4)\n", "print(result)\n" ], "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "qgscfBCpVP6p", "outputId": "14da8df0-6a46-456e-8954-ca971ed57052" }, "execution_count": null, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "128\n" ] } ] }, { "cell_type": "markdown", "source": [ "## The best cases to use decorators are when you need to:\n", "\n", "* Add functionality to an existing function or class without modifying its source code directly. This is useful when you want to keep the original code intact and separate concerns.\n", "* Implement cross-cutting concerns such as logging, timing, caching, or authentication that apply to multiple functions or classes. With decorators, you can write the logic once and apply it to any function or class that needs it.\n", "* Implement design patterns such as decorators, adapters, or facades that wrap existing code to provide additional functionality or interface. Decorators are particularly useful for implementing the decorator pattern, where you wrap a base object with one or more decorators to add or modify its behavior.\n", "* Ensure that a function or class adheres to certain rules or constraints. For example, you can use a decorator to validate input arguments or enforce access control policies.\n", "* Provide syntactic sugar or alternative interfaces to existing functions or classes. For example, you can use a decorator to make a function more readable by adding a descriptive name or keyword argument.\n", "\n", "Best example for decorators would be the routing in Flask\n", "\n", " from flask import Flask\n", "\n", " app = Flask(__name__)\n", "\n", " @app.route('/')\n", " def hello_world():\n", " return 'Hello, World!'\n", "\n" ], "metadata": { "id": "G4NRwOWVa48L" } } ] }