Learn More About Closures

PythonPythonBeginner
Practice Now

This tutorial is from open-source community. Access the source code

Introduction

Objectives:

  • Learn more about closures

In this section, we look briefly at a few of the more unusual aspects of closures.


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL python(("`Python`")) -.-> python/BasicConceptsGroup(["`Basic Concepts`"]) python(("`Python`")) -.-> python/ControlFlowGroup(["`Control Flow`"]) python(("`Python`")) -.-> python/DataStructuresGroup(["`Data Structures`"]) python(("`Python`")) -.-> python/FunctionsGroup(["`Functions`"]) python(("`Python`")) -.-> python/ModulesandPackagesGroup(["`Modules and Packages`"]) python(("`Python`")) -.-> python/ObjectOrientedProgrammingGroup(["`Object-Oriented Programming`"]) python(("`Python`")) -.-> python/ErrorandExceptionHandlingGroup(["`Error and Exception Handling`"]) python/BasicConceptsGroup -.-> python/comments("`Comments`") python/BasicConceptsGroup -.-> python/variables_data_types("`Variables and Data Types`") python/BasicConceptsGroup -.-> python/numeric_types("`Numeric Types`") python/BasicConceptsGroup -.-> python/strings("`Strings`") python/ControlFlowGroup -.-> python/conditional_statements("`Conditional Statements`") python/DataStructuresGroup -.-> python/tuples("`Tuples`") python/DataStructuresGroup -.-> python/sets("`Sets`") python/FunctionsGroup -.-> python/function_definition("`Function Definition`") python/FunctionsGroup -.-> python/lambda_functions("`Lambda Functions`") python/FunctionsGroup -.-> python/scope("`Scope`") python/ModulesandPackagesGroup -.-> python/importing_modules("`Importing Modules`") python/ModulesandPackagesGroup -.-> python/using_packages("`Using Packages`") python/ObjectOrientedProgrammingGroup -.-> python/classes_objects("`Classes and Objects`") python/ObjectOrientedProgrammingGroup -.-> python/constructor("`Constructor`") python/ObjectOrientedProgrammingGroup -.-> python/polymorphism("`Polymorphism`") python/ObjectOrientedProgrammingGroup -.-> python/encapsulation("`Encapsulation`") python/ErrorandExceptionHandlingGroup -.-> python/raising_exceptions("`Raising Exceptions`") python/BasicConceptsGroup -.-> python/python_shell("`Python Shell`") python/FunctionsGroup -.-> python/build_in_functions("`Build-in Functions`") subgraph Lab Skills python/comments -.-> lab-132506{{"`Learn More About Closures`"}} python/variables_data_types -.-> lab-132506{{"`Learn More About Closures`"}} python/numeric_types -.-> lab-132506{{"`Learn More About Closures`"}} python/strings -.-> lab-132506{{"`Learn More About Closures`"}} python/conditional_statements -.-> lab-132506{{"`Learn More About Closures`"}} python/tuples -.-> lab-132506{{"`Learn More About Closures`"}} python/sets -.-> lab-132506{{"`Learn More About Closures`"}} python/function_definition -.-> lab-132506{{"`Learn More About Closures`"}} python/lambda_functions -.-> lab-132506{{"`Learn More About Closures`"}} python/scope -.-> lab-132506{{"`Learn More About Closures`"}} python/importing_modules -.-> lab-132506{{"`Learn More About Closures`"}} python/using_packages -.-> lab-132506{{"`Learn More About Closures`"}} python/classes_objects -.-> lab-132506{{"`Learn More About Closures`"}} python/constructor -.-> lab-132506{{"`Learn More About Closures`"}} python/polymorphism -.-> lab-132506{{"`Learn More About Closures`"}} python/encapsulation -.-> lab-132506{{"`Learn More About Closures`"}} python/raising_exceptions -.-> lab-132506{{"`Learn More About Closures`"}} python/python_shell -.-> lab-132506{{"`Learn More About Closures`"}} python/build_in_functions -.-> lab-132506{{"`Learn More About Closures`"}} end

Closures as a data structure

One potential use of closures is as a tool for data encapsulation. Try this example:

def counter(value):
    def incr():
        nonlocal value
        value += 1
        return value

    def decr():
        nonlocal value
        value -= 1
        return value

    return incr, decr

This code defines two inner functions that manipulate a value. Try it out:

>>> up, down = counter(0)
>>> up()
1
>>> up()
2
>>> up()
3
>>> down()
2
>>> down()
1
>>>

Notice how there is no class definition involved here. Moreover, there is no global variable either. Yet, the up() and down() functions are manipulating some "behind the scenes" value. It's fairly magical.

Closures as a code generator

In Exercise 4.3, you developed a collection of descriptor classes that allowed type-checking of object attributes. For example:

class Stock:
    name = String()
    shares = Integer()
    price = Float()

This kind of thing can also be implemented using closures. Define a file typedproperty.py and put the following code in it:

## typedproperty.py

def typedproperty(name, expected_type):
    private_name = '_' + name

    @property
    def value(self):
        return getattr(self, private_name)

    @value.setter
    def value(self, val):
        if not isinstance(val, expected_type):
            raise TypeError(f'Expected {expected_type}')
        setattr(self, private_name, val)

    return value

This look pretty wild, but the function is effectively making code. You'd use it in a class definition like this:

from typedproperty import typedproperty

class Stock:
    name = typedproperty('name', str)
    shares = typedproperty('shares', int)
    price = typedproperty('price', float)

    def __init__(self, name, shares, price):
        self.name = name
        self.shares = shares
        self.price = price

Verify that this class performs type-checking in the same way as the descriptor code.

Add function String(), Integer(), and Float() to the typedproperty.py file so that you can write the following code:

from typedproperty import String, Integer, Float

class Stock:
    name = String('name')
    shares = Integer('shares')
    price = Float('price')
    def __init__(self, name, shares, price):
        self.name = name
        self.shares = shares
        self.price = price

Challenge: Eliminating names

Modify the typedproperty.py code so that attribute names are no-longer required:

from typedproperty import String, Integer, Float

class Stock:
    name = String()
    shares = Integer()
    price = Float()
    def __init__(self, name, shares, price):
        self.name = name
        self.shares = shares
        self.price = price

Hint: To do this, recall the __set_name__() method of descriptor objects that gets called when descriptors are placed in a class definition.

Summary

Congratulations! You have completed the Learn More About Closures lab. You can practice more labs in LabEx to improve your skills.

Other Python Tutorials you may like