2 - Python Decorators and Descriptors
Enroll to start learning
Youβve not yet enrolled in this course. Please enroll for free to listen to audio lessons, classroom podcasts and take practice test.
Interactive Audio Lesson
Listen to a student-teacher conversation explaining the topic in a relatable way.
Introduction to Decorators
π Unlock Audio Lesson
Sign up and enroll to listen to this audio lesson
Today, we'll learn about decorators in Python! Can anyone tell me what a decorator is?
Is it something that modifies a function's behavior?
Exactly! A decorator is a design pattern that allows modifying a function or method without changing its source code. Think of it as a wrapper that adds behavior to our functions. Can you remember this with the acronym 'WAP', which stands for 'Wrap and Add Behavior'?
How does it work?
Great question! A decorator takes a function as an input and returns a new function. For example, letβs say we have a simple function that prints 'Hello'. With a decorator, we can print messages before and after calling that function.
Can you show an example?
Sure! Letβs look at the code that shows a basic decorator. Remember, anytime you see '@decorator_name' before a function definition, it's being decorated!
In summary, decorators allow us to extend functions without modifying their code directly.
Function Decorators: Concept and Usage
π Unlock Audio Lesson
Sign up and enroll to listen to this audio lesson
Now that we understand the concept of decorators, letβs discuss their practical applications. Can anyone think of how we might use decorators?
Maybe for logging function calls?
Correct! We can create a logging decorator that logs function details. Here's an example that demonstrates this. It shows the function call parameters and return values.
That sounds useful! It could help in debugging.
Absolutely! Logging helps track what happens during execution. To remember this, think 'WALD' - 'Wrap, Add Log Details'.
What are other applications of decorators?
Decorators can also handle access control, memoization, timing, and more. Each of these enhances our functions in unique ways.
To wrap up, decorators provide versatile solutions across programming needs - remember their potential!
Decorators with Parameters
π Unlock Audio Lesson
Sign up and enroll to listen to this audio lesson
Now, let's talk about decorators that accept parameters. Why might we want a decorator with arguments?
It could help to customize the behavior of the decorator based on input values!
Exactly! For instance, we might want a decorator that repeats a function call a specific number of times. We can achieve this using nested functions.
Can you show that example?
Sure! Let's see how we can create a repeat decorator. Remember that 'RAP' helps you remember 'Repeat And Parameterize'.
Got it! This looks powerful because we can customize the number of times a function is executed.
Yes, parameters broaden the utility of decorators. Keep practicing this concept!
Class Decorators
π Unlock Audio Lesson
Sign up and enroll to listen to this audio lesson
Letβs shift gears and explore class decorators. Can anyone explain how a class decorator differs from a function decorator?
Is it because it modifies or wraps the entire class instead of just a single function?
Exactly right! Class decorators can add attributes or even modify methods across the class.
Do you have an example?
Yes! Letβs consider a class decorator that adds an attribute. To remember this, think 'CADA' - 'Class Attribute Design Add'.
Wow, thatβs cool! Can decorators help with things like logging too?
Absolutely. We can log all methods within a class, making tracking even easier. Great observation!
Descriptors: Protocol and Custom Descriptors
π Unlock Audio Lesson
Sign up and enroll to listen to this audio lesson
Finally, weβll explore descriptors. What do you think a descriptor does?
Is it related to managing attribute access in a class?
Yes! Descriptors allow you to define methods for getting, setting, and deleting attributes. This provides enhanced control over how attributes behave.
Could we create a descriptor that enforces types?
Absolutely! Descriptors can be tailored to enforce validation. Remember the term 'VAD' for 'Validate Attribute Data' to help remember this.
This seems powerful for ensuring data integrity!
Indeed! Implementing descriptors can greatly enhance our class designs. Keep practicing these concepts!
Introduction & Overview
Read summaries of the section's main ideas at different levels of detail.
Quick Overview
Standard
The section covers the purpose and use of decorators for altering function behaviors, and descriptors for managing attribute access in classes. It includes examples for function decorators, class decorators, and built-in decorators like @property, @staticmethod, and @classmethod, along with an overview of the descriptor protocol.
Detailed
Detailed Summary
In this section, we explore Python decorators and descriptors which provide powerful tools for modifying functions and managing attributes in classes.
Decorators
- Definition: A decorator is a higher-order function that allows for the alteration of a functionβs behavior without modifying its source code. It wraps the original function, adding pre- or post-processing tasks.
- Basic Usage: Decorators can be used with a simple syntax, by prefixing a function definition with
@decorator_name, leading to concise and readable code.
Examples:
- Basic Decorator: Demonstrates how decorators can log messages around a functionβs execution.
- Parameterized Decorators: Decorators can also accept arguments, requiring nested functions for implementation.
- Class Decorators: They can add attributes or wrap methods within a class, expanding their functionality.
Built-in Decorators:
- @property, @staticmethod, @classmethod: These decorators facilitate attribute management and class method definitions, enhancing encapsulation and organization.
Descriptors
- Definition: Descriptors are objects that define methods
__get__,__set__, and__delete__, providing precise control over how attributes are accessed. - Usage: Useful for implementing type checks, validation, and lazy evaluations in class attributes.
Custom Descriptors:
This section concludes with custom descriptors that enforce type constraints on attributes, an essential technique for data integrity in classes.
Overall, mastering decorators and descriptors enables more elegant and efficient coding practices in Python.
Youtube Videos
Audio Book
Dive deep into the subject with an immersive audiobook experience.
Introduction to Decorators
Chapter 1 of 10
π Unlock Audio Chapter
Sign up and enroll to access the full audio experience
Chapter Content
A decorator in Python is a design pattern that allows you to modify the behavior of a function or class method without changing its source code. Itβs a higher-order function that takes a function as input and returns a new function with added or altered behavior. Think of a decorator as a wrapper that "decorates" a function by extending or modifying its behavior.
Detailed Explanation
Decorators are functions that enhance another function's behavior without changing its actual code. To visualize it, imagine you have a beautifully wrapped gift (the function) inside a box (the decorator) that adds special features like glitter and stickers (enhancements), making it more attractive when presented. When you unwrap the gift, the core remains unchanged.
Examples & Analogies
Think about how a chef can add spices to a dish without changing its primary ingredients. Just like spices can enhance flavor, decorators enhance function behavior.
Basic Function Decorator Example
Chapter 2 of 10
π Unlock Audio Chapter
Sign up and enroll to access the full audio experience
Chapter Content
def my_decorator(func):
def wrapper():
print("Before the function runs")
func()
print("After the function runs")
return wrapper
def say_hello():
print("Hello!")
decorated_func = my_decorator(say_hello)
decorated_func()
Output:
Before the function runs Hello! After the function runs
Using the @ syntax for decorators:
@my_decorator
def say_hello():
print("Hello!")
say_hello()
This is equivalent to say_hello = my_decorator(say_hello).
Detailed Explanation
This chunk presents an example of a basic decorator in action. The my_decorator function takes another function, wraps it in a new function (the 'wrapper'), and modifies its behavior by printing messages before and after the wrapped function runs. The @ syntax is a shorthand for applying decorators, making the code easy to read and maintain.
Examples & Analogies
Imagine a ticket at a movie theater where the ticket signifies entry into the show (the base function). The usher (the decorator) checks your ticket at the door, adds some popcorn to your experience, and then lets you in. The core activity of watching the movie (the original function) remains unchanged.
Function Decorators: Concept and Usage
Chapter 3 of 10
π Unlock Audio Chapter
Sign up and enroll to access the full audio experience
Chapter Content
Decorators are widely used to add logging, access control, memoization, timing, and more.
Example: Logging Decorator
def logger(func):
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__} with args {args} and kwargs {kwargs}")
result = func(*args, **kwargs)
print(f"{func.__name__} returned {result}")
return result
return wrapper
@logger
def add(a, b):
return a + b
add(3, 4)
Output:
Calling add with args (3, 4) and kwargs {}
add returned 7
Notice how the wrapper preserves the arguments and return value.
Detailed Explanation
This section explains the practical uses of decorators, particularly for logging function calls. The logger decorator records when a function is called, its parameters, and what it returns, providing valuable insights for debugging or monitoring. The decorator wraps any function it decorates, adding an extra layer of functionality without modifying the function's code.
Examples & Analogies
Think of a security camera in a shopping mall. The camera records video of all customer activity (logging) but doesn't interfere with the customers' shopping experience (the core function). This way, you can review footage if any issues arise, just like you would use logs to troubleshoot code.
Decorators with Parameters
Chapter 4 of 10
π Unlock Audio Chapter
Sign up and enroll to access the full audio experience
Chapter Content
Sometimes decorators themselves need parameters. This requires an extra level of nested functions.
Example: Decorator with Arguments
def repeat(n):
def decorator(func):
def wrapper(*args, **kwargs):
for _ in range(n):
func(*args, **kwargs)
return wrapper
return decorator
@repeat(3)
def greet(name):
print(f"Hello, {name}!")
greet("Alice")
This produces:
Hello, Alice! Hello, Alice! Hello, Alice!
Explanation:
β repeat(3) returns decorator, which receives greet.
β decorator returns wrapper, which calls greet 3 times.
Detailed Explanation
This chunk introduces decorators that require parameters for additional flexibility. In the example, repeat takes a number n and creates a decorator that makes the decorated function run n times. This involves creating two nested functions: one for the decorator itself and another to wrap the target function.
Examples & Analogies
Picture a trainer at a sports camp who has students practice a drill multiple times. The trainer criticizes and retests after every repetition (the decorator with parameters), ensuring better performance. Here, the greet function is practiced three times to make sure the students learn effectively.
Class Decorators
Chapter 5 of 10
π Unlock Audio Chapter
Sign up and enroll to access the full audio experience
Chapter Content
Decorators can also be applied to classes. They receive the class as an argument and can modify or replace it.
Example: Class Decorator that Adds an Attribute
def add_class_attr(cls):
cls.extra_attribute = "Added by decorator"
return cls
@add_class_attr
class MyClass:
pass
print(MyClass.extra_attribute) # Output: Added by decorator
Example: Class Decorator That Wraps Methods
def method_logger(cls):
for attr_name, attr_value in cls.__dict__.items():
if callable(attr_value):
setattr(cls, attr_name, logger(attr_value))
return cls
@method_logger
class Calculator:
def add(self, x, y):
return x + y
calc = Calculator()
calc.add(5, 7)
This applies the earlier logger decorator to all methods.
Detailed Explanation
This section covers how decorators can be used with classes. Class decorators can modify class attributes or wrap methods in a similar way to function decorators. One example adds an extra attribute to a class, while another example demonstrates wrapping all methods of a class with a logging functionality. This shows decorators' versatility and how they enhance class behavior.
Examples & Analogies
Envision a teacher (the decorator) who brings in extra materials for a class (the decorated class). In the first instance, the teacher adds a new resource (attribute), and in the second, the teacher ensures all students keep a journal of their learning (logging method calls).
Built-in Decorators: @property, @staticmethod, and @classmethod
Chapter 6 of 10
π Unlock Audio Chapter
Sign up and enroll to access the full audio experience
Chapter Content
Python provides some powerful built-in decorators that are commonly used to manage class behaviors.
2.5.1 @property
@property allows you to use methods like attributes. Itβs a way to add getter, setter, and deleter methods in a Pythonic way.
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def radius(self):
return self._radius
@radius.setter
def radius(self, value):
if value <= 0:
raise ValueError("Radius must be positive")
self._radius = value
@property
def area(self):
import math
return math.pi * (self._radius ** 2)
c = Circle(5)
print(c.radius) # 5
print(c.area) # 78.53981633974483
c.radius = 10
print(c.area) # 314.1592653589793
Using @property improves encapsulation by allowing validation and calculation without changing the attribute access syntax.
Detailed Explanation
The @property decorator enables class methods to behave like attributes, allowing for attributes to be dynamically calculated or validated. In the example, Circle uses properties to control access to its radius and area, ensuring that the radius remains positive. This encapsulation helps maintain data integrity while providing a clean interface.
Examples & Analogies
Imagine a modern thermostat that adjusts the home temperature based on the current weather. The desired climate is set (the property), and changes are made automatically without needing to access and adjust the internal mechanisms, keeping users engaged without exposing the complex systems.
Static Methods and Class Methods
Chapter 7 of 10
π Unlock Audio Chapter
Sign up and enroll to access the full audio experience
Chapter Content
2.5.2 @staticmethod
A static method is a method inside a class that does not operate on an instance or the class itself.
class Math:
@staticmethod
def add(x, y):
return x + y
print(Math.add(3, 4)) # 7
Static methods are used for utility functions logically related to the class.
2.5.3 @classmethod
Class methods receive the class (cls) as the first argument and can modify class state.
class Person:
population = 0
def __init__(self, name):
self.name = name
Person.population += 1
@classmethod
def get_population(cls):
return cls.population
p1 = Person("Alice")
p2 = Person("Bob")
print(Person.get_population()) # 2
Detailed Explanation
These sections explain @staticmethod and @classmethod. A static method doesn't need access to an instance or class; it's like a regular function that lives inside a class. A class method, on the other hand, has access to the class state and can modify it. This functionality allows classes to manage data differently depending on their needs.
Examples & Analogies
Think of static methods as kitchen tools like a blender (which can prepare various foods without any reliance on a particular meal). Class methods are like an administrator who oversees operations (the class) for all activities (instances) happening in a building, keeping track of records and statistics.
Descriptor Protocol: __get__, __set__, and __delete__
Chapter 8 of 10
π Unlock Audio Chapter
Sign up and enroll to access the full audio experience
Chapter Content
What is a Descriptor?
A descriptor is an object attribute with βbinding behavior,β meaning it defines methods to control attribute access. Descriptors are the low-level mechanism behind properties, methods, and more.
The descriptor protocol defines three methods:
β __get__(self, instance, owner) β Retrieve the attribute value.
β __set__(self, instance, value) β Set the attribute value.
β __delete__(self, instance) β Delete the attribute.
Descriptors are used as class variables. When accessed via an instance, Python calls the descriptorβs methods.
Example: Simple Descriptor
class Descriptor:
def __get__(self, instance, owner):
print("Getting attribute")
return instance._value
def __set__(self, instance, value):
print("Setting attribute")
instance._value = value
def __delete__(self, instance):
print("Deleting attribute")
del instance._value
class MyClass:
attr = Descriptor()
obj = MyClass()
obj.attr = 42 # Setting attribute
print(obj.attr) # Getting attribute, Output: 42
del obj.attr # Deleting attribute
Detailed Explanation
The descriptor is a more advanced mechanism that controls how attributes are accessed. The __get__, __set__, and __delete__ methods allow precise control over getting, setting, and deleting values of attributes defined in a class. In the example, a Descriptor class manages the access to an attribute _value, providing additional functionality like logging each action.
Examples & Analogies
Imagine a library system where books (attributes) have strict control (descriptors). When you borrow a book, the librarian records who checks it out (getting), adds tracking for overdue returns (setting), and removes it from the system when returned (deleting). This ensures the library keeps accurate records while managing the access of its collection.
Creating Custom Descriptors for Attribute Management
Chapter 9 of 10
π Unlock Audio Chapter
Sign up and enroll to access the full audio experience
Chapter Content
Descriptors provide fine control over attribute access. Letβs create a descriptor that validates attribute values.
Example: Typed Attribute Descriptor
class Typed:
def __init__(self, name, expected_type):
self.name = name
self.expected_type = expected_type
def __get__(self, instance, owner):
if instance is None:
return self
return instance.__dict__[self.name]
def __set__(self, instance, value):
if not isinstance(value, self.expected_type):
raise TypeError(f"Expected {self.expected_type}")
instance.__dict__[self.name] = value
def __delete__(self, instance):
raise AttributeError("Can't delete attribute")
class Person:
name = Typed('name', str)
age = Typed('age', int)
def __init__(self, name, age):
self.name = name
self.age = age
p = Person("Alice", 30)
print(p.name) # Alice
p.age = 31 # Works fine
# p.age = "Thirty" # Raises TypeError: Expected
Detailed Explanation
This chunk introduces custom descriptors that enforce specific data types for class attributes. The Typed descriptor checks if the assigned value matches the expected type, raising an error if it doesn't. This provides a powerful way to control attribute values, ensuring that class instances maintain valid states.
Examples & Analogies
Think of a strict school where students (class instances) are enrolled in specific grades (attribute types). Each student must show they meet the requirements for their gradeβif a student claims to be in fifth grade but hasn't learned necessary skills, they won't be allowed to proceed, similar to how the Typed descriptor prevents invalid assignments.
Summary of Key Concepts
Chapter 10 of 10
π Unlock Audio Chapter
Sign up and enroll to access the full audio experience
Chapter Content
In this chapter, you learned:
β What decorators are, and how to use function, parameterized, and class decorators.
β Pythonβs built-in decorators @property, @staticmethod, and @classmethod for managing class behaviors.
β The descriptor protocol (get, set, delete), the foundation of attribute management in Python.
β How to create custom descriptors to add controlled, reusable attribute access logic such as validation and type checking.
Detailed Explanation
This section summarizes the key points covered, emphasizing the significance of decorators and descriptors in Python programming. Understanding these concepts enables developers to write cleaner, more maintainable code that is easier to extend and debug.
Examples & Analogies
Consider a toolkit that includes various specialized tools (decorators and descriptors) for different tasks in a workshop. Knowing which tools to use for specific jobs helps craftsmen work more efficiently, just as decorators and descriptors help programmers manage and control code behavior effectively.
Key Concepts
-
Decorators: Functions that wrap other functions to modify their behavior without altering their source codes.
-
Function Decorators: Specifically designed decorators for modifying standard functions, with various applications.
-
Class Decorators: Decorators that can modify or enhance class definitions instead of single functions.
-
Descriptors: Objects that customize the behavior of class attributes through defined methods.
Examples & Applications
Basic Decorator: Demonstrates how decorators can log messages around a functionβs execution.
Parameterized Decorators: Decorators can also accept arguments, requiring nested functions for implementation.
Class Decorators: They can add attributes or wrap methods within a class, expanding their functionality.
Built-in Decorators:
@property, @staticmethod, @classmethod: These decorators facilitate attribute management and class method definitions, enhancing encapsulation and organization.
Descriptors
Definition: Descriptors are objects that define methods __get__, __set__, and __delete__, providing precise control over how attributes are accessed.
Usage: Useful for implementing type checks, validation, and lazy evaluations in class attributes.
Custom Descriptors:
This section concludes with custom descriptors that enforce type constraints on attributes, an essential technique for data integrity in classes.
Overall, mastering decorators and descriptors enables more elegant and efficient coding practices in Python.
Memory Aids
Interactive tools to help you remember key concepts
Rhymes
To decorate we wrap tight, adding flair, making functions right.
Stories
Imagine a house where decorators change the paint, and each time you enter, there's something new. That's how decorators modify functions!
Memory Tools
Use 'VAD' for Descriptors: Validate Attribute Data when using them to implement controlled access.
Acronyms
WAP
Wrap and Add Behavior for remembering what decorators do.
Flash Cards
Glossary
- Decorator
A higher-order function that modifies a function or method's behavior without changing its source code.
- Descriptor
An object attribute with binding behavior, defining methods to control attribute access in classes.
- Higherorder function
A function that takes another function as an argument or returns a function as a result.
- @property
A built-in decorator that allows methods to be accessed as attributes, providing property-like behavior.
- @staticmethod
A built-in decorator that defines a method that does not operate on an instance or class.
- @classmethod
A built-in decorator that defines a method that receives the class as the first argument.
Reference links
Supplementary resources to enhance your learning experience.