From c7f165fa424bf1df4779566ae71abc4ac8889678 Mon Sep 17 00:00:00 2001 From: Drishan Gupta <66329991+drishangupta@users.noreply.github.com> Date: Sun, 12 May 2024 01:30:53 +0530 Subject: [PATCH 1/6] Create decorator-kwargs-args.md WIP --- .../advanced-python/decorator-kwargs-args.md | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 contrib/advanced-python/decorator-kwargs-args.md diff --git a/contrib/advanced-python/decorator-kwargs-args.md b/contrib/advanced-python/decorator-kwargs-args.md new file mode 100644 index 00000000..9a9ab933 --- /dev/null +++ b/contrib/advanced-python/decorator-kwargs-args.md @@ -0,0 +1,93 @@ +# Advanced Python +## Functions as First class objects +Functions in Python are so called first class objects, which means they can be treated as variables, viz. functions can be used as arguments or they can be returned using the return keyword. + +**Example** + +```python +def func1(): + def func2(): + print("Printing from the inner function, func2") + return func2 + +``` +Assigning func1 to function_call object +```python +function_call=func1() +``` +Calling the function +```python +>> function_call() +``` +**Output** +``` +Printing from the inner function, func2 +``` +Here we have seen the use of function as a first class object, func2 was returned as the result of the execution of the outer function, func1. + +## *args +\* is an iterating operator used to unpack datatypes such as lists, tuples etc. +**For example** +```python +tuple1=(1,2,4,5,6,7) +print(tuple1) +print(*tuple1) +``` +In the above we have defined a tuple called tuple1 with the items (1,2,4,5,6,7). +First we print normally and the output for that is: +``` +(1, 2, 4, 5, 6, 7) + +``` +Then we print with the \* operator, where we will get the output as: +``` +1 2 4 5 6 7 +``` + +Here the \* operator has unpacked the tuple, tuple1. + +Now that you have understood why \* is used, we can take a look at *args. *args is used in functions so that positional arguments are stored in the variable args. *args is just a naming convention, *anything can be used +*args makes python functions flexible to handle dynamic arguments. +```python +def test1(*args): + print(args) + print(f"The number of elements in args = {len(args)}") +a=list(range(0,10)) +test1(*a) +``` +In the above snippet, we are sending a list of numbers to the test function which returns the following output: +``` +(0, 1, 2, 3, 4, 5, 6, 7, 8, 9) +The number of elements in args = 10 +``` +If in the test1 we do not use \* in the argument + +```python +def test1(*args): + print(args) + print(f"The number of elements in args = {len(args)}") +a=list(range(0,10)) +test1(a) +``` +we get the following result. This is a tuple containing a list. +``` +([0, 1, 2, 3, 4, 5, 6, 7, 8, 9],) +The number of elements in args = 1 +``` +## **kwargs +**kwargs stands for keyword arguments. This is used for key and value pairs and similar to *args, this makes functions flexible enough to handle dynamic key value pairs in arguments. +```python +def test2(**kwargs): + print(kwargs) + print(f"The number of elements in kwargs = {len(kwargs)}") +test2(a=1,b=2,c=3,d=4,e=5) +``` +The above snippet uses some key-value pairs and out test2 function gives the following output: +``` +{'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5} +The number of elements in kwargs = 5 +``` +A dictionary with keys and values is obtained. + +## Decorators (@decorators) +Now that we understand what first class object, *args, **kwargs is, we can move to decorators. From bd0adc17fcc30dd619b59df1ea7c042b65a47801 Mon Sep 17 00:00:00 2001 From: Drishan Gupta <66329991+drishangupta@users.noreply.github.com> Date: Sun, 12 May 2024 03:23:39 +0530 Subject: [PATCH 2/6] Update decorator-kwargs-args.md --- .../advanced-python/decorator-kwargs-args.md | 55 ++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/contrib/advanced-python/decorator-kwargs-args.md b/contrib/advanced-python/decorator-kwargs-args.md index 9a9ab933..91f1eb44 100644 --- a/contrib/advanced-python/decorator-kwargs-args.md +++ b/contrib/advanced-python/decorator-kwargs-args.md @@ -90,4 +90,57 @@ The number of elements in kwargs = 5 A dictionary with keys and values is obtained. ## Decorators (@decorators) -Now that we understand what first class object, *args, **kwargs is, we can move to decorators. +Now that we understand what first class object, *args, **kwargs is, we can move to decorators. Decorators are used to perform a task that needs to be performed for existing functions. If some task has to be performed for each function, we can write a function which will perform the task without us having to make changes in each function. + +**Sample Code:** +``` +import time +def multiplication(a,b): + start=time.time() + c=a*b + total=time.time()-start + print("Time taken for execution of multiplication",total) + return c + +def addition(a,b): + start=time.time() + c=a+b + total=time.time()-start + print("Time taken for execution of addition ",total) + return c + +multiplication(4,5) +addition(4,5) +``` + +In the above code, we had to calculate time and print the execution time seperately for each function leading to repeatation of code. This is where decorators come in handy. +The same functionality can be achieved with the help of a decorator. + +**Here's how:** +``` +import time +def time_find(function): + def wrapper(*args, **kwargs): + starttime=time.time() + function(*args, **kwargs) + total=time.time()-starttime + print(f"Time Taken by {function.__name__} to run is ",total) + return wrapper + +@time_find #to use a decorator, simply use @ above a function. +def multiply(a, b): + print(a*b) + +@time_find +def addition(a,b): + print(a+b) + +multiply(4,5) +addition(4,5) +``` + +The above method eleminates redundant code and makes the code cleaner. You may have observed that we have used *args and **kwargs in the wrapper function. This is so that this decorator function is flexible for all types of functions and their parameters and this way it can find out the execution time of any function with as many parameters as needed, we just need to use our decorator @time_find. + + + + From 55f8871a9e3a21dace285a7ea12dcc786b2e00c9 Mon Sep 17 00:00:00 2001 From: Drishan Gupta <66329991+drishangupta@users.noreply.github.com> Date: Sun, 12 May 2024 03:24:56 +0530 Subject: [PATCH 3/6] Update index.md --- contrib/advanced-python/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/advanced-python/index.md b/contrib/advanced-python/index.md index 82596a2f..dac98905 100644 --- a/contrib/advanced-python/index.md +++ b/contrib/advanced-python/index.md @@ -1,3 +1,3 @@ # List of sections -- [Section title](filename.md) +- [Decorators/\*args/**kwargs](decorator-kwargs-args) From 4c979af5410b8bff9e9d14e1f79580c0abf42023 Mon Sep 17 00:00:00 2001 From: Drishan Gupta <66329991+drishangupta@users.noreply.github.com> Date: Sun, 12 May 2024 03:26:49 +0530 Subject: [PATCH 4/6] Updated the index --- contrib/advanced-python/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/advanced-python/index.md b/contrib/advanced-python/index.md index dac98905..5ea5081c 100644 --- a/contrib/advanced-python/index.md +++ b/contrib/advanced-python/index.md @@ -1,3 +1,3 @@ # List of sections -- [Decorators/\*args/**kwargs](decorator-kwargs-args) +- [Decorators/\*args/**kwargs](decorator-kwargs-args.md) From a324545579de03abf78245aea4e24c2baf3d0440 Mon Sep 17 00:00:00 2001 From: Drishan Gupta <66329991+drishangupta@users.noreply.github.com> Date: Sun, 12 May 2024 13:50:31 +0530 Subject: [PATCH 5/6] Update decorator-kwargs-args.md --- contrib/advanced-python/decorator-kwargs-args.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/advanced-python/decorator-kwargs-args.md b/contrib/advanced-python/decorator-kwargs-args.md index 91f1eb44..402dcc6f 100644 --- a/contrib/advanced-python/decorator-kwargs-args.md +++ b/contrib/advanced-python/decorator-kwargs-args.md @@ -93,7 +93,7 @@ A dictionary with keys and values is obtained. Now that we understand what first class object, *args, **kwargs is, we can move to decorators. Decorators are used to perform a task that needs to be performed for existing functions. If some task has to be performed for each function, we can write a function which will perform the task without us having to make changes in each function. **Sample Code:** -``` +```python import time def multiplication(a,b): start=time.time() @@ -117,7 +117,7 @@ In the above code, we had to calculate time and print the execution time seperat The same functionality can be achieved with the help of a decorator. **Here's how:** -``` +```python import time def time_find(function): def wrapper(*args, **kwargs): From 378dbddc67a91ecebc6ccfb1fb38a8d45feaa0e2 Mon Sep 17 00:00:00 2001 From: Drishan Gupta <66329991+drishangupta@users.noreply.github.com> Date: Tue, 14 May 2024 08:44:20 +0530 Subject: [PATCH 6/6] Removed Formatting errors --- .../advanced-python/decorator-kwargs-args.md | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/contrib/advanced-python/decorator-kwargs-args.md b/contrib/advanced-python/decorator-kwargs-args.md index 402dcc6f..63a41b36 100644 --- a/contrib/advanced-python/decorator-kwargs-args.md +++ b/contrib/advanced-python/decorator-kwargs-args.md @@ -6,9 +6,9 @@ Functions in Python are so called first class objects, which means they can be t ```python def func1(): - def func2(): - print("Printing from the inner function, func2") - return func2 + def func2(): + print("Printing from the inner function, func2") + return func2 ``` Assigning func1 to function_call object @@ -17,7 +17,7 @@ function_call=func1() ``` Calling the function ```python ->> function_call() +>>> function_call() ``` **Output** ``` @@ -50,8 +50,8 @@ Now that you have understood why \* is used, we can take a look at *args. *args *args makes python functions flexible to handle dynamic arguments. ```python def test1(*args): - print(args) - print(f"The number of elements in args = {len(args)}") + print(args) + print(f"The number of elements in args = {len(args)}") a=list(range(0,10)) test1(*a) ``` @@ -64,8 +64,8 @@ If in the test1 we do not use \* in the argument ```python def test1(*args): - print(args) - print(f"The number of elements in args = {len(args)}") + print(args) + print(f"The number of elements in args = {len(args)}") a=list(range(0,10)) test1(a) ``` @@ -78,8 +78,8 @@ The number of elements in args = 1 **kwargs stands for keyword arguments. This is used for key and value pairs and similar to *args, this makes functions flexible enough to handle dynamic key value pairs in arguments. ```python def test2(**kwargs): - print(kwargs) - print(f"The number of elements in kwargs = {len(kwargs)}") + print(kwargs) + print(f"The number of elements in kwargs = {len(kwargs)}") test2(a=1,b=2,c=3,d=4,e=5) ``` The above snippet uses some key-value pairs and out test2 function gives the following output: @@ -96,18 +96,18 @@ Now that we understand what first class object, *args, **kwargs is, we can move ```python import time def multiplication(a,b): - start=time.time() - c=a*b - total=time.time()-start - print("Time taken for execution of multiplication",total) - return c + start=time.time() + c=a*b + total=time.time()-start + print("Time taken for execution of multiplication",total) + return c def addition(a,b): - start=time.time() - c=a+b - total=time.time()-start - print("Time taken for execution of addition ",total) - return c + start=time.time() + c=a+b + total=time.time()-start + print("Time taken for execution of addition ",total) + return c multiplication(4,5) addition(4,5)