From d9aace20950de2a4ddc0fed145290eb2982a3513 Mon Sep 17 00:00:00 2001 From: Tapan Pandey Date: Wed, 24 Jul 2024 22:20:15 +0530 Subject: [PATCH 01/14] started with function decorators --- fluent/chap.07/chapter.07.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 fluent/chap.07/chapter.07.md diff --git a/fluent/chap.07/chapter.07.md b/fluent/chap.07/chapter.07.md new file mode 100644 index 0000000..4242208 --- /dev/null +++ b/fluent/chap.07/chapter.07.md @@ -0,0 +1,10 @@ +# Function decorators and closures + +Function decorators allow us to mark the functions in the source code in some way to enhance their working. + +## Decorator 101 + +Decorator is a function that takes another function as an argument and: + +- performs some processing with the passed function. +- can return it or replaces it with the another function. From b1e83c4900810520599b7689583bad5a97f4613b Mon Sep 17 00:00:00 2001 From: Tapan Pandey Date: Thu, 22 Aug 2024 22:21:35 +0530 Subject: [PATCH 02/14] Updated the documentation for the chapter07.md: --- fluent/chap.07/chapter.07.md | 43 ++++++++++++++++++++++++++++++++ fluent/chap.07/code/decorator.py | 7 ++++++ 2 files changed, 50 insertions(+) create mode 100644 fluent/chap.07/code/decorator.py diff --git a/fluent/chap.07/chapter.07.md b/fluent/chap.07/chapter.07.md index 4242208..4e793ca 100644 --- a/fluent/chap.07/chapter.07.md +++ b/fluent/chap.07/chapter.07.md @@ -8,3 +8,46 @@ Decorator is a function that takes another function as an argument and: - performs some processing with the passed function. - can return it or replaces it with the another function. + +So, lets say we have an existing function named `decorate` and it can take another function as argument then: + +```python +def target(): + print("Running target") + +target = decorate(target) +``` + +is same as: + +```python +@decorate +def target(): + print("Running target") +``` + +So let's now write the definition of our decorator `decorate` and see how it replaces the original function: + +```python +def decorate(func): + print("Running original function inside the decorator") + func() + def inner(): + print("Running inner") + return inner +``` + +and here is the sample run of decoration: + +```bash +>>> from decorator import decorate +>>> def foo(): +... print("This is foo...") +... +>>> foo = decorate(foo) +Running the passed function inside the decorator +This is foo... +>>> foo() +The inner function +``` + diff --git a/fluent/chap.07/code/decorator.py b/fluent/chap.07/code/decorator.py new file mode 100644 index 0000000..af6505d --- /dev/null +++ b/fluent/chap.07/code/decorator.py @@ -0,0 +1,7 @@ +def decorate(func): + print("Running the passed function inside the decorator") + func() + + def inner(): + print("The inner function") + return inner From e6b0c08096e2591ffa8ac5ea8fe3d798411094a5 Mon Sep 17 00:00:00 2001 From: Tapan Pandey Date: Mon, 2 Sep 2024 21:58:00 +0530 Subject: [PATCH 03/14] Added example for the the generator show import difference --- fluent/chap.07/chapter.07.md | 32 +++++++++++++++++++++++++++++ fluent/chap.07/code/__init__.py | 0 fluent/chap.07/code/registration.py | 23 +++++++++++++++++++++ fluent/chap.07/code/when.py | 28 +++++++++++++++++++++++++ 4 files changed, 83 insertions(+) create mode 100644 fluent/chap.07/code/__init__.py create mode 100644 fluent/chap.07/code/registration.py create mode 100644 fluent/chap.07/code/when.py diff --git a/fluent/chap.07/chapter.07.md b/fluent/chap.07/chapter.07.md index 4e793ca..239980b 100644 --- a/fluent/chap.07/chapter.07.md +++ b/fluent/chap.07/chapter.07.md @@ -51,3 +51,35 @@ This is foo... The inner function ``` +The docorator executes as soon as we assign the decorator to a variable, that's why we're seeing the lines: + +```bash +Running the passed function inside the decorator +This is foo... +``` + +However it returns the definition of the inner function and that doesn't gets executed but rather the address gets returned and assigned to the variable outside. + +In the example above the `decorate` is the decorator function and the `foo` is the decorated function. + +```python3 +>>> from code.registration import register +registering to registry +registering to registry +>>> from code.registration import f1, f2, f3 +>>> f1() +Running f1 +>>> f2() +Running f2 +>>> f3() +Running f3 +>>> from code.registration import registry +>>> registry +[, ] +>>> registry[0] + +>>> registry[0]() +Running f1 +>>> registry[1]() +Running f2 +``` diff --git a/fluent/chap.07/code/__init__.py b/fluent/chap.07/code/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/fluent/chap.07/code/registration.py b/fluent/chap.07/code/registration.py new file mode 100644 index 0000000..8641abc --- /dev/null +++ b/fluent/chap.07/code/registration.py @@ -0,0 +1,23 @@ +# Example that shows difference between the runtime and the rgistration time +# for a decorator. + +registry = [] + +def register (funcptr): + print("registering %s to registry" % funcptr) + registry.append(funcptr) + return funcptr + +# Lets now decorate two functions + +@register +def f1(): + print("Running f1") + +@register +def f2(): + print("Running f2") + +def f3(): + print("Running f3") + diff --git a/fluent/chap.07/code/when.py b/fluent/chap.07/code/when.py new file mode 100644 index 0000000..f98539e --- /dev/null +++ b/fluent/chap.07/code/when.py @@ -0,0 +1,28 @@ +registry = [] + +def register(func): + print("Registering the function %s" % func) + registry.append(func) + return func + +@register +def f1(): + print("Running f1") + +@register +def f2(): + print("Running f2") + +def f3(): + print("Running f3") + + +def main(): + print("Running main") + print("Registery ->", registry) + f1() + f2() + f3() + +if __name__ == '__main__': + main() From c2920bfd3786e1c8b1a19a9e850003f7bb358ff0 Mon Sep 17 00:00:00 2001 From: Tapan Pandey Date: Wed, 4 Sep 2024 21:59:14 +0530 Subject: [PATCH 04/14] What si decorator --- fluent/chap.07/chapter.07.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/fluent/chap.07/chapter.07.md b/fluent/chap.07/chapter.07.md index 239980b..52b9c43 100644 --- a/fluent/chap.07/chapter.07.md +++ b/fluent/chap.07/chapter.07.md @@ -62,6 +62,41 @@ However it returns the definition of the inner function and that doesn't gets ex In the example above the `decorate` is the decorator function and the `foo` is the decorated function. +## When does Python executes a decorator + +Python executes a decorator as soon as the decorator is defined. For decorator this is usually the **import time**. In the code below we have: + +- Defined the function `registration` that is basically a decorator that appends the function address to the list `registry` and returns the decorated function that is basically the same as originally called function +- Defined three functions i.e. `f1`, `f2` and `f3` out of which first two are decorated ones. +- Whenever we are calling the decorator it initially prints what all functions it is decorating. + +```python +# Example that shows difference between the runtime and the registration time +# for a decorator. + +registry = [] + +def register (funcptr): + print("registering %s to registry" % funcptr) + registry.append(funcptr) + return funcptr + +# Lets now decorate two functions + +@register +def f1(): + print("Running f1") + +@register +def f2(): + print("Running f2") + +def f3(): + print("Running f3") +``` + +In the code below we are importing the `register`. As we import the register from the module we also apply it to the `f1` and `f2`. The register gets executed as soon as it is imported. That is why we see the lines `registering .... ` twice since we are decorating two functions. + ```python3 >>> from code.registration import register registering to registry From 762bfe1992454707cfb320aae9584e3e28df3020 Mon Sep 17 00:00:00 2001 From: Tapan Pandey Date: Tue, 17 Sep 2024 22:20:57 +0530 Subject: [PATCH 05/14] Started with the local scope --- fluent/chap.07/chapter.07.md | 7 +++++++ fluent/chap.07/code/registration.py | 7 +++++-- fluent/chap.07/code/registration_alone.py | 10 +++++++++ .../chap.07/code/registration_candidates.py | 21 +++++++++++++++++++ fluent/chap.07/code/when.py | 5 +++++ 5 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 fluent/chap.07/code/registration_alone.py create mode 100644 fluent/chap.07/code/registration_candidates.py diff --git a/fluent/chap.07/chapter.07.md b/fluent/chap.07/chapter.07.md index 52b9c43..9653228 100644 --- a/fluent/chap.07/chapter.07.md +++ b/fluent/chap.07/chapter.07.md @@ -118,3 +118,10 @@ Running f1 >>> registry[1]() Running f2 ``` + +Remember that most decorators do change the passed function, replacing it with a inner function. **Code that uses the inner function almost always depends upon the closures**. Let's see the how variable scope works to understand the closures. + + +## Variable scope rules in Python + + diff --git a/fluent/chap.07/code/registration.py b/fluent/chap.07/code/registration.py index 8641abc..dbeaaa7 100644 --- a/fluent/chap.07/code/registration.py +++ b/fluent/chap.07/code/registration.py @@ -3,21 +3,24 @@ registry = [] -def register (funcptr): + +def register(funcptr): print("registering %s to registry" % funcptr) registry.append(funcptr) return funcptr # Lets now decorate two functions + @register def f1(): print("Running f1") + @register def f2(): print("Running f2") + def f3(): print("Running f3") - diff --git a/fluent/chap.07/code/registration_alone.py b/fluent/chap.07/code/registration_alone.py new file mode 100644 index 0000000..e909dae --- /dev/null +++ b/fluent/chap.07/code/registration_alone.py @@ -0,0 +1,10 @@ +# Example that shows difference between the runtime and the rgistration time +# for a decorator. + +registry = [] + + +def register(funcptr): + print("registering %s to registry" % funcptr) + registry.append(funcptr) + return funcptr diff --git a/fluent/chap.07/code/registration_candidates.py b/fluent/chap.07/code/registration_candidates.py new file mode 100644 index 0000000..175a77a --- /dev/null +++ b/fluent/chap.07/code/registration_candidates.py @@ -0,0 +1,21 @@ +from registration_alone import register +# Lets now decorate two functions + + +@register +def f1(): + print("Running f1") + + +@register +def f2(): + print("Running f2") + + +def f3(): + print("Running f3") + +if __name__ == '__main__': + f1() + f2() + f3() diff --git a/fluent/chap.07/code/when.py b/fluent/chap.07/code/when.py index f98539e..22618ff 100644 --- a/fluent/chap.07/code/when.py +++ b/fluent/chap.07/code/when.py @@ -1,18 +1,22 @@ registry = [] + def register(func): print("Registering the function %s" % func) registry.append(func) return func + @register def f1(): print("Running f1") + @register def f2(): print("Running f2") + def f3(): print("Running f3") @@ -24,5 +28,6 @@ def main(): f2() f3() + if __name__ == '__main__': main() From 27fb908bccbba69e048ea320df4d878d42d9b5d6 Mon Sep 17 00:00:00 2001 From: Tapan Pandey Date: Fri, 20 Sep 2024 22:28:49 +0530 Subject: [PATCH 06/14] Started with the example code for the local variable scope. --- fluent/chap.07/chapter.07.md | 44 ++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/fluent/chap.07/chapter.07.md b/fluent/chap.07/chapter.07.md index 9653228..3b391c5 100644 --- a/fluent/chap.07/chapter.07.md +++ b/fluent/chap.07/chapter.07.md @@ -123,5 +123,49 @@ Remember that most decorators do change the passed function, replacing it with a ## Variable scope rules in Python +Consider the following function `f1` below. This function defines a local variable `a` that is passed as the parameter. It then prints the variable `a` and the vaiable `b` that is not defined anywhere in the program: + +```pycon +>>> def f1(a): +... print(a) +... print(b) +... +>>> f1(3) +3 +Traceback (most recent call last): + File "", line 1, in + File "", line 3, in f1 +NameError: name 'b' is not defined +``` + +When trying to print the variable `b` we are getting `NameError` stating that `b` is not defined anywhere, either locally in the function or globally in the program. + +Now as a next step we defined `b` as a variable in the global namespace and called the function `f1` again: + +```pycon +>>> b = 6 +>>> f1(3) +3 +6 +``` + +This time as anticipated we are getting the correct values for the `a` and `b`. +```pycon +>>> b = 12 +>>> def f2(a): +... print(a) +... print(b) +... b = 16 +... +``` +```pycon +>>> f2(4) +4 +Traceback (most recent call last): + File "", line 1, in + File "", line 3, in f2 +UnboundLocalError: cannot access local variable 'b' where it is not associated with a value +>>> +``` From 792616c7b349831fc491710184afd2062d428cf3 Mon Sep 17 00:00:00 2001 From: Tapan Pandey Date: Mon, 23 Sep 2024 21:59:15 +0530 Subject: [PATCH 07/14] Local and global variables --- fluent/chap.07/chapter.07.md | 42 ++++++++++++++++++++++++++++++++---- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/fluent/chap.07/chapter.07.md b/fluent/chap.07/chapter.07.md index 3b391c5..afa4476 100644 --- a/fluent/chap.07/chapter.07.md +++ b/fluent/chap.07/chapter.07.md @@ -125,7 +125,7 @@ Remember that most decorators do change the passed function, replacing it with a ## Variable scope rules in Python Consider the following function `f1` below. This function defines a local variable `a` that is passed as the parameter. It then prints the variable `a` and the vaiable `b` that is not defined anywhere in the program: -```pycon +```python >>> def f1(a): ... print(a) ... print(b) @@ -142,7 +142,7 @@ When trying to print the variable `b` we are getting `NameError` stating that `b Now as a next step we defined `b` as a variable in the global namespace and called the function `f1` again: -```pycon +```python >>> b = 6 >>> f1(3) 3 @@ -151,7 +151,10 @@ Now as a next step we defined `b` as a variable in the global namespace and call This time as anticipated we are getting the correct values for the `a` and `b`. -```pycon +Next, we've defined a function `f2` that first prints the value of `a`, `b` and further tries to assign a new value to the `b`. +This function however doesn't passes beyond the first `print` statement: + +```python >>> b = 12 >>> def f2(a): ... print(a) @@ -160,7 +163,9 @@ This time as anticipated we are getting the correct values for the `a` and `b`. ... ``` -```pycon +This happened because when Python compiled the body of the function `f2` it decided that `b` is a local variable rather than a global variable. Thus it tries to fetch the value of the `b` from within the local environment and dicovers that `b` is not defined it is unbound. + +```python >>> f2(4) 4 Traceback (most recent call last): @@ -169,3 +174,32 @@ Traceback (most recent call last): UnboundLocalError: cannot access local variable 'b' where it is not associated with a value >>> ``` + +If we want the Python interpreter to treat the `b` as global variable inspite of the assignment in the function we can redefine the function with the `b` being prefixed with `global` keyword: + +```python +>>> b = 9 +>>> def f2(a): +... global b +... print(a) +... print(b) +... b = 12 +``` + +Now the execution of `f2` goes as desired: + +```python +>>> f2(3) +3 +9 +>>> b +12 +>>> b = 14 +>>> b +14 +>>> f2(1) +1 +14 +>>> b +12 +``` From 2dd6f20aa546ae9689b4a387697ec61dff6b8169 Mon Sep 17 00:00:00 2001 From: Tapan Pandey Date: Tue, 24 Sep 2024 22:21:34 +0530 Subject: [PATCH 08/14] Writing a OO averager --- fluent/chap.07/chapter.07.md | 6 ++++++ fluent/chap.07/code/average_oo.py | 7 +++++++ 2 files changed, 13 insertions(+) create mode 100644 fluent/chap.07/code/average_oo.py diff --git a/fluent/chap.07/chapter.07.md b/fluent/chap.07/chapter.07.md index afa4476..7c422f6 100644 --- a/fluent/chap.07/chapter.07.md +++ b/fluent/chap.07/chapter.07.md @@ -203,3 +203,9 @@ Now the execution of `f2` goes as desired: >>> b 12 ``` + +## Closures + +Let us first see the example code below that computes the average value of a ever increasing series: + + diff --git a/fluent/chap.07/code/average_oo.py b/fluent/chap.07/code/average_oo.py new file mode 100644 index 0000000..45045cf --- /dev/null +++ b/fluent/chap.07/code/average_oo.py @@ -0,0 +1,7 @@ +class Averager(): + def __init__(self): + self.series = [] + + def __call__(self, value): + self.series.append(value) + return sum(self.series)/len(self.series) From f75e8f819882a3a187e64bc5a6235190f4577db3 Mon Sep 17 00:00:00 2001 From: Tapan Pandey Date: Wed, 25 Sep 2024 22:07:40 +0530 Subject: [PATCH 09/14] closure code completed --- fluent/chap.07/chapter.07.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/fluent/chap.07/chapter.07.md b/fluent/chap.07/chapter.07.md index 7c422f6..b42644d 100644 --- a/fluent/chap.07/chapter.07.md +++ b/fluent/chap.07/chapter.07.md @@ -208,4 +208,20 @@ Now the execution of `f2` goes as desired: Let us first see the example code below that computes the average value of a ever increasing series: +```python +class Averager(): + def __init__(self): + self.series = [] + + def __call__(self, value): + self.series.append(value) + return sum(self.series)/len(self.series) +``` + +So in the code above we have created an attribute `series` that stores the number in the series and we have overloaded the dunder `__call__` to make it a callable. + +Now we're going to do the samething with a higher order function + +A higher-order function is a function that either takes one or more functions as arguments or returns a function as its result. +A closure is a function that captures the lexical scope in which it was created. This means that a closure remembers the environment where it was defined, even when it is executed outside that scope. Closures are often used in conjunction with higher-order functions. From 0cf1da3d6584ee38a126cfe2519717fc01d9e24d Mon Sep 17 00:00:00 2001 From: Tapan Pandey Date: Tue, 1 Oct 2024 22:13:06 +0530 Subject: [PATCH 10/14] Onto closures --- fluent/chap.07/chapter.07.md | 42 ++++++++++++++++++++++++-- fluent/chap.07/code/average_closure.py | 8 +++++ 2 files changed, 47 insertions(+), 3 deletions(-) create mode 100644 fluent/chap.07/code/average_closure.py diff --git a/fluent/chap.07/chapter.07.md b/fluent/chap.07/chapter.07.md index b42644d..538d81b 100644 --- a/fluent/chap.07/chapter.07.md +++ b/fluent/chap.07/chapter.07.md @@ -220,8 +220,44 @@ class Averager(): So in the code above we have created an attribute `series` that stores the number in the series and we have overloaded the dunder `__call__` to make it a callable. -Now we're going to do the samething with a higher order function +**Now we're going to do the same thing with a higher order function** -A higher-order function is a function that either takes one or more functions as arguments or returns a function as its result. -A closure is a function that captures the lexical scope in which it was created. This means that a closure remembers the environment where it was defined, even when it is executed outside that scope. Closures are often used in conjunction with higher-order functions. +A **higher-order function** is a function that either takes one or more functions as arguments or returns a function as its result. In that sense all the decorators that we've define are basically higher order function, as they take a function as an argument and return a function as a result. + +A **closure is a function** that *captures the lexical scope** in which it was created. This means that a closure remembers the environment where it was defined, even when it is executed outside that scope. Closures are often used in conjunction with higher-order functions. + +Now lets create the `Averager` using a functional approach: + +```python +def make_averager(): + series = [] + + def averager(value): + series.append(value) + return sum(series) / len(series) + + return averager +``` + +The `averager` in the code above is returned back during the call to the `make_averager`. We can make use of the returned function `avg` just like the way we did in last example: + +```python +>>> from average_closure import make_averager +>>> avg = make_averager() +>>> avg(10) +10.0 +>>> avg(11) +10.5 +>>> avg(12) +11.0 +>>> avg(13) +11.5 +``` + +It is worth to understand how this time `avg` manages the series of the numbers. In the last example where we used the class we have `series` maintained as a attribute. + +This time we have `series` defined and maintained as local variable of the function `make_averager`. Now when we are calling `avg`, we are actually calling the `averager` and `make_average` is already gone back. Comes question how we're accessing the `series` inside the `average`. + +Within the `averager` the `series` is known as the local variable i.e. a variable that is not bound to the local scope. + diff --git a/fluent/chap.07/code/average_closure.py b/fluent/chap.07/code/average_closure.py new file mode 100644 index 0000000..60d01ff --- /dev/null +++ b/fluent/chap.07/code/average_closure.py @@ -0,0 +1,8 @@ +def make_averager(): + series = [] + + def averager(value): + series.append(value) + return sum(series) / len(series) + + return averager From c9225d27b7daef4ecd62865107ec33b50a78ea8c Mon Sep 17 00:00:00 2001 From: Tapan Pandey Date: Tue, 1 Oct 2024 22:44:52 +0530 Subject: [PATCH 11/14] More on closures --- fluent/chap.07/chapter.07.md | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/fluent/chap.07/chapter.07.md b/fluent/chap.07/chapter.07.md index 538d81b..4aba43a 100644 --- a/fluent/chap.07/chapter.07.md +++ b/fluent/chap.07/chapter.07.md @@ -259,5 +259,21 @@ It is worth to understand how this time `avg` manages the series of the numbers. This time we have `series` defined and maintained as local variable of the function `make_averager`. Now when we are calling `avg`, we are actually calling the `averager` and `make_average` is already gone back. Comes question how we're accessing the `series` inside the `average`. -Within the `averager` the `series` is known as the local variable i.e. a variable that is not bound to the local scope. +Within the `averager` the `series` is known as the free variable i.e. a variable that is not bound to the local scope but is equally accessible in that scope. + +All the local and the free variables are kept under the `__code__` attribute that actually represents the compiled body of the function: + +```python +>>> from average_closure import make_averager +>>> avg = make_averager() +>>> avg(10) +10.0 +>>> avg.__code__.co_varnames +('value',) +>>> avg.__code__.co_freevars +('series',) +``` + +So, closures have free variable, this means a set of variables that are out of scope for closure but are accessible within the closure. Closure retains the binding to the free variables that can be used later when the function has returned. + From b9948ba14d7b970a566dc2dd5352ea83ded5e3e2 Mon Sep 17 00:00:00 2001 From: Tapan Pandey Date: Thu, 3 Oct 2024 22:11:14 +0530 Subject: [PATCH 12/14] Completed the code for the closures --- fluent/chap.07/chapter.07.md | 53 +++++++++++++++++++++++ fluent/chap.07/code/average_closure_ii.py | 11 +++++ 2 files changed, 64 insertions(+) create mode 100644 fluent/chap.07/code/average_closure_ii.py diff --git a/fluent/chap.07/chapter.07.md b/fluent/chap.07/chapter.07.md index 4aba43a..2ffaf48 100644 --- a/fluent/chap.07/chapter.07.md +++ b/fluent/chap.07/chapter.07.md @@ -276,4 +276,57 @@ All the local and the free variables are kept under the `__code__` attribute tha So, closures have free variable, this means a set of variables that are out of scope for closure but are accessible within the closure. Closure retains the binding to the free variables that can be used later when the function has returned. +We can make the code for the `averager` a bit better by removing the `series` list and instead keeping the sum and computing the average like this: +```python +def make_averager(): + count = 0 + total = 0 + + def averager(new_value): + total += new_value + count += 1 + return total / count + + return averager +``` + +And now we can call the function like this: + +```python +>>> avg = make_averager() +>>> avg(10) +Traceback (most recent call last): + File "", line 1, in + total += new_value + ^^^^^ +UnboundLocalError: cannot access local variable 'total' where it is not associated with a value +``` + +So, again we're getting the `UnboundLocalError`, and this is because whenever we access the immutable values like integers, lists or the tuples this is considered as accessing a variable even before it is declared. This is equally same as accessing a global variable inside a function without even giving it the global declaration. + +we can correct the code as follows: + +```python +def make_averager(): + count = 0 + total = 0 + + def averager(new_value): + nonlocal count, total + total += new_value + count += 1 + return total / count + + return averager +``` + +And now when we'll run the code we will get the result: + +```python +>>> avg = make_averager() +>>> avg(10) +10.0 +>>> avg(11) +10.5 +``` diff --git a/fluent/chap.07/code/average_closure_ii.py b/fluent/chap.07/code/average_closure_ii.py new file mode 100644 index 0000000..d641cd0 --- /dev/null +++ b/fluent/chap.07/code/average_closure_ii.py @@ -0,0 +1,11 @@ +def make_averager(): + count = 0 + total = 0 + + def averager(new_value): + nonlocal count, total + total += new_value + count += 1 + return total / count + + return averager From 72ea6d63285bceb6933e87951add1aacf09fb58c Mon Sep 17 00:00:00 2001 From: Tapan Pandey Date: Fri, 4 Oct 2024 21:59:22 +0530 Subject: [PATCH 13/14] Written a decorator clocked --- fluent/chap.07/chapter.07.md | 6 ++++++ fluent/chap.07/code/clockdeco.py | 24 ++++++++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 fluent/chap.07/code/clockdeco.py diff --git a/fluent/chap.07/chapter.07.md b/fluent/chap.07/chapter.07.md index 2ffaf48..287126a 100644 --- a/fluent/chap.07/chapter.07.md +++ b/fluent/chap.07/chapter.07.md @@ -330,3 +330,9 @@ And now when we'll run the code we will get the result: >>> avg(11) 10.5 ``` + +## Creating a simple decorator in the Python + +So as per our last discussion we saw that closures are a way to bound veriables out of scope as well as returning an inner function that completely replaces the definiton of function to what it is assigned. + +- Remember that decorator takes a function as an argument and completely replaces it with a new function. diff --git a/fluent/chap.07/code/clockdeco.py b/fluent/chap.07/code/clockdeco.py new file mode 100644 index 0000000..3747bb4 --- /dev/null +++ b/fluent/chap.07/code/clockdeco.py @@ -0,0 +1,24 @@ +import time + +def clock(funcptr): + ''' + The clock decorator calculates how much time it takes to execute a function + @param funcptr: The decorated function + ''' + + def clocked(**args): + ''' + Clocked is the inner function that is the replacement for the funcptr + Arguments passed to the funcptr are actually captured in the clocked + ''' + + t0 = time.perf_counter() + result = funcptr(*args) + t1 = time.perf_counter() - t0 + name = funcptr.__name__ + arg_str = ','.join(repr(arg) for arg in args) + print("[%0.8f] %s(%s) -> %r" % (t1, name, arg_str, result)) + + return result + + return clocked From 63b2de706ef6b744f307bc0832cebf141c2ffc5f Mon Sep 17 00:00:00 2001 From: Tapan Pandey Date: Mon, 7 Oct 2024 21:38:17 +0530 Subject: [PATCH 14/14] Definition of the clock decorator completed --- fluent/chap.07/chapter.07.md | 53 ++++++++++++++++++++++++++++++++ fluent/chap.07/code/clockdeco.py | 5 +-- fluent/chap.07/code/clockdemo.py | 7 +++++ 3 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 fluent/chap.07/code/clockdemo.py diff --git a/fluent/chap.07/chapter.07.md b/fluent/chap.07/chapter.07.md index 287126a..9b7e381 100644 --- a/fluent/chap.07/chapter.07.md +++ b/fluent/chap.07/chapter.07.md @@ -336,3 +336,56 @@ And now when we'll run the code we will get the result: So as per our last discussion we saw that closures are a way to bound veriables out of scope as well as returning an inner function that completely replaces the definiton of function to what it is assigned. - Remember that decorator takes a function as an argument and completely replaces it with a new function. +- Decorator basically follows the rule of the closure and relies on the free variable concept to get access to variables defined outside the scope. + +In the example below we've defined a decorator `clock` that basically shows how much time it took to execute a function: + +```python +import time + +def clock(funcptr): + ''' + The clock decorator calculates how much time it takes to execute a function + @param funcptr: The decorated function + ''' + + def clocked(**args): + ''' + Clocked is the inner function that is the replacement for the funcptr + Arguments passed to the funcptr are actually captured in the clocked + ''' + + t0 = time.perf_counter() + result = funcptr(*args) + t1 = time.perf_counter() - t0 + name = funcptr.__name__ + arg_str = ','.join(repr(arg) for arg in args) + print("[%0.8f] %s(%s) -> %r" % (t1, name, arg_str, result)) + + return result + + return clocked +``` + +In the line `result = funcptr(*args)`, we're able to access the `args` only because closure is having access to the free variable. + +Here is the example below how we can use the `clocked`: + +```python +>>> from clockdeco import clock +>>> +>>> @clock +... def factorial(n: int) -> int: +... return 1 if n < 2 else n * factorial(n -1) +... +>>> factorial(6) +[0.00000125] factorial(1) -> 1 +[0.00005714] factorial(2) -> 2 +[0.00007815] factorial(3) -> 6 +[0.00009683] factorial(4) -> 24 +[0.00011541] factorial(5) -> 120 +[0.00013862] factorial(6) -> 720 +720 +``` + +This is typical behaviour of a decorator. It replaces the decorated function with a new definition. It however *usually* accepts the same arguments as passed to the original function and returns the same value as by original function. diff --git a/fluent/chap.07/code/clockdeco.py b/fluent/chap.07/code/clockdeco.py index 3747bb4..94a7500 100644 --- a/fluent/chap.07/code/clockdeco.py +++ b/fluent/chap.07/code/clockdeco.py @@ -1,17 +1,18 @@ import time + def clock(funcptr): ''' The clock decorator calculates how much time it takes to execute a function @param funcptr: The decorated function ''' - def clocked(**args): + def clocked(*args): ''' Clocked is the inner function that is the replacement for the funcptr Arguments passed to the funcptr are actually captured in the clocked ''' - + t0 = time.perf_counter() result = funcptr(*args) t1 = time.perf_counter() - t0 diff --git a/fluent/chap.07/code/clockdemo.py b/fluent/chap.07/code/clockdemo.py new file mode 100644 index 0000000..5d5f2e7 --- /dev/null +++ b/fluent/chap.07/code/clockdemo.py @@ -0,0 +1,7 @@ +import time + +from clockdeco import clocked + +@clock +def factorial (n: int) -> int: + return 1 if n < 2 else return n * factorial(n - 1)