Decorator Chaining and Parameterized Decorators

PythonPythonBeginner
Practice Now

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

Introduction

Objectives:

  • Decorator chaining
  • Defining decorators that accept arguments.

Files Modified: logcall.py, validate.py


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL python(("`Python`")) -.-> python/BasicConceptsGroup(["`Basic Concepts`"]) python(("`Python`")) -.-> python/FunctionsGroup(["`Functions`"]) python(("`Python`")) -.-> python/ControlFlowGroup(["`Control Flow`"]) python(("`Python`")) -.-> python/DataStructuresGroup(["`Data Structures`"]) 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/FunctionsGroup -.-> python/keyword_arguments("`Keyword Arguments`") 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/ControlFlowGroup -.-> python/for_loops("`For Loops`") python/DataStructuresGroup -.-> python/lists("`Lists`") python/DataStructuresGroup -.-> python/tuples("`Tuples`") python/DataStructuresGroup -.-> python/dictionaries("`Dictionaries`") python/DataStructuresGroup -.-> python/sets("`Sets`") python/FunctionsGroup -.-> python/function_definition("`Function Definition`") python/FunctionsGroup -.-> python/default_arguments("`Default Arguments`") python/ModulesandPackagesGroup -.-> python/importing_modules("`Importing Modules`") python/ModulesandPackagesGroup -.-> python/using_packages("`Using Packages`") python/ModulesandPackagesGroup -.-> python/standard_libraries("`Common Standard Libraries`") 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/catching_exceptions("`Catching Exceptions`") 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-132515{{"`Decorator Chaining and Parameterized Decorators`"}} python/keyword_arguments -.-> lab-132515{{"`Decorator Chaining and Parameterized Decorators`"}} python/variables_data_types -.-> lab-132515{{"`Decorator Chaining and Parameterized Decorators`"}} python/numeric_types -.-> lab-132515{{"`Decorator Chaining and Parameterized Decorators`"}} python/strings -.-> lab-132515{{"`Decorator Chaining and Parameterized Decorators`"}} python/conditional_statements -.-> lab-132515{{"`Decorator Chaining and Parameterized Decorators`"}} python/for_loops -.-> lab-132515{{"`Decorator Chaining and Parameterized Decorators`"}} python/lists -.-> lab-132515{{"`Decorator Chaining and Parameterized Decorators`"}} python/tuples -.-> lab-132515{{"`Decorator Chaining and Parameterized Decorators`"}} python/dictionaries -.-> lab-132515{{"`Decorator Chaining and Parameterized Decorators`"}} python/sets -.-> lab-132515{{"`Decorator Chaining and Parameterized Decorators`"}} python/function_definition -.-> lab-132515{{"`Decorator Chaining and Parameterized Decorators`"}} python/default_arguments -.-> lab-132515{{"`Decorator Chaining and Parameterized Decorators`"}} python/importing_modules -.-> lab-132515{{"`Decorator Chaining and Parameterized Decorators`"}} python/using_packages -.-> lab-132515{{"`Decorator Chaining and Parameterized Decorators`"}} python/standard_libraries -.-> lab-132515{{"`Decorator Chaining and Parameterized Decorators`"}} python/classes_objects -.-> lab-132515{{"`Decorator Chaining and Parameterized Decorators`"}} python/constructor -.-> lab-132515{{"`Decorator Chaining and Parameterized Decorators`"}} python/polymorphism -.-> lab-132515{{"`Decorator Chaining and Parameterized Decorators`"}} python/encapsulation -.-> lab-132515{{"`Decorator Chaining and Parameterized Decorators`"}} python/catching_exceptions -.-> lab-132515{{"`Decorator Chaining and Parameterized Decorators`"}} python/raising_exceptions -.-> lab-132515{{"`Decorator Chaining and Parameterized Decorators`"}} python/python_shell -.-> lab-132515{{"`Decorator Chaining and Parameterized Decorators`"}} python/build_in_functions -.-> lab-132515{{"`Decorator Chaining and Parameterized Decorators`"}} end

Copying Metadata

When a function gets wrapped by a decorator, you often lose information about the name of the function, documentation strings, and other details. Verify this:

>>> @logged
    def add(x,y):
        'Adds two things'
        return x+y

>>> add
<function wrapper at 0x4439b0>
>>> help(add)
... look at the output ...
>>>

Fix the definition of the logged decorator so that it copies function metadata properly. To do this, use the @wraps(func) decorator as shown in the notes.

After you're done, make sure the decorator preserves the function name and doc string.

>>> @logged
    def add(x,y):
        'Adds two things'
        return x+y

>>> add
<function add at 0x4439b0>
>>> add.__doc__
'Adds two things'
>>>

Fix the @validated decorator you wrote earlier so that it also preserves metadata using @wraps(func).

Your first decorator with arguments

The @logged decorator you defined earlier always just prints a simple message with the function name. Suppose that you wanted the user to be able to specify a custom message of some sort.

Define a new decorator @logformat(fmt) that accepts a format string as an argument and uses fmt.format(func=func) to format a supplied function into a log message:

## sample.py
...
from logcall import logformat

@logformat('{func.__code__.co_filename}:{func.__name__}')
def mul(x,y):
    return x*y

To do this, you need to define a decorator that takes an argument. This is what it should look like when you test it:

>>> import sample
Adding logging to add
Adding logging to sub
Adding logging to mul
>>> sample.add(2,3)
Calling add
5
>>> sample.mul(2,3)
sample.py:mul
6
>>>

To further simplify the code, show how you can define the original @logged decorator using the the @logformat decorator.

Multiple decorators and methods

Things can get a bit dicey when decorators are applied to methods in a class. Try applying your @logged decorator to the methods in the following class.

class Spam:
    @logged
    def instance_method(self):
        pass

    @logged
    @classmethod
    def class_method(cls):
        pass

    @logged
    @staticmethod
    def static_method():
        pass

    @logged
    @property
    def property_method(self):
        pass

Does it even work at all? (hint: no). Is there any way to fix the code so that it works? For example, can you make it so the following example works?

>>> s = Spam()
>>> s.instance_method()
instance_method
>>> Spam.class_method()
class_method
>>> Spam.static_method()
static_method
>>> s.property_method
property_method
>>>

Validation (Redux)

In the last exercise, you wrote a @validated decorator that enforced type annotations. For example:

@validated
def add(x: Integer, y:Integer) -> Integer:
    return x + y

Make a new decorator @enforce() that enforces types specified via keyword arguments to the decorator instead. For example:

@enforce(x=Integer, y=Integer, return_=Integer)
def add(x, y):
    return x + y

The resulting behavior of the decorated function should be identical. Note: Make the return_ keyword specify the return type. return is a Python reserved word so you have to pick a slightly different name.

Discussion

Writing robust decorators is often a lot harder than it looks. Recommended reading:

Summary

Congratulations! You have completed the Decorator Chaining lab. You can practice more labs in LabEx to improve your skills.

Other Python Tutorials you may like