curriculum/challenges/english/blocks/lecture-understanding-object-oriented-programming-and-encapsulation/68c128cbd77e4ba9ed671937.md
Getters and setters are methods that let you control how the attributes of a class are accessed and modified. With getters you retrieve a value, and with setters you set a value.
These actions are done through what's known as properties. They are what connect getters and setters, and allow access to data.
Properties act like attributes but behave like methods under the hood. Think of them as data you define like methods, but work like attributes. This means you can access properties with dot notation instead of parentheses or round brackets.
The main thing properties do is that they run extra logic behind the scenes when you get, set, or delete values with them. This makes them the perfect choice when you want to access or manipulate data within objects.
So why use properties for that instead of methods? It's mostly about readability and convention. They make your code cleaner and easier to read.
When you use a method, you always have to call it with parentheses. But with a property, you can access it just like a normal attribute using dot notation. That makes your code look simple even when it is doing extra work behind the scenes.
For example, you might want to calculate a value or check that a new value is valid before saving it. Instead of calling a method for that, you can use an attribute-like way to do that.
In Python, a decorator is a function that modifies the functionalities of other functions, or classes, without changing their original code.
While it is possible to create custom decorators, this is beyond the scope of this lesson. Just know that to create a property, you define a method and place the @property decorator above it. This turns the method into a property, so it can be accessed like an attribute while internally calling the decorated method.
That takes us to getters. Here's how to create one with the @property decorator:
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def radius(self): # A getter to get the radius
return self._radius
@property
def area(self): # A getter to calculate area
return 3.14 * (self._radius ** 2)
my_circle = Circle(3)
print(my_circle.radius) # 3
print(my_circle.area) # 28.26
This example gets a radius and the area of a circle.
Notice how we used _radius instead of radius inside the class. The underscore is a common Python convention to show that an attribute is meant to be private. In other words, it signifies that it's for internal use and should not be accessed directly from outside the class.
To make a setter to create the radius, for example, you have to define another method with the same name and use @<property_name>.setter above it:
Using self.radius inside __init__ ensures the setter runs during object creation, so invalid radius values are caught immediately.
class Circle:
def __init__(self, radius):
self.radius = radius # Calling the setter
@property
def radius(self): # A getter to get the radius
return self._radius
@radius.setter
def radius(self, value): # A setter to set the radius
if value <= 0:
raise ValueError('Radius must be positive')
self._radius = value
my_circle = Circle(3)
print('Initial radius:', my_circle.radius) # Initial radius: 3
my_circle.radius = 8
print('After modifying the radius:', my_circle.radius) # After modifying the radius: 8
In this example, the radius setter is not just setting the radius for the circle, it's also running a validation that makes sure the radius is not a negative number.
Once you define getters and setters, Python automatically calls them under the hood whenever you use normal attribute syntax:
my_circle.radius # This will call the getter
my_circle.radius = 4 # This will call the setter
Note that inside the setter, you cannot use same name of the property when assigning a new value. That's because self.radius = value will call the setter within the setter method itself, leading to infinite recursion and a RecursionError. So you must always use the underscore-prefixed form self._radius = value.
Just like you can control how an attribute is accessed through getter and how it is modified with setter, you can also control how it is deleted using a deleter.
A deleter runs custom logic when you use the del statement on a property. To create one, you use the @<property_name>.deleter decorator:
class Circle:
def __init__(self, radius):
self.radius = radius
# Getter
@property
def radius(self):
return self._radius
# Setter
@radius.setter
def radius(self, value):
if value <= 0:
raise ValueError("Radius must be positive")
self._radius = value
# Deleter
@radius.deleter
def radius(self):
print("Deleting radius...")
del self._radius
Here's how the deleter can be put to use:
# Create circle object with a radius
my_circle = Circle(33)
print("Initial radius:", my_circle.radius) # 33
# Delete the radius
# This calls the deleter
del my_circle.radius # Deleting radius...
print("Radius deleted!") # Radius deleted!
# Try to access radius after deletion
try:
print(my_circle.radius)
except AttributeError as e:
print("Error:", e) # Error: 'Circle' object has no attribute '_radius'
The takeaway from this is that:
What lets you run logic behind the scenes while getting or setting an attribute's value?
Importing external modules.
Think about methods that allow validation or computation when reading and writing data.
Class inheritance.
Think about methods that allow validation or computation when reading and writing data.
Properties
Direct attribute access.
Think about methods that allow validation or computation when reading and writing data.
3
What ties getters and setters together so you can execute logic while maintaining dot notation access?
Properties
Decorators
Think about a feature that lets you use methods like attributes with simple dot syntax.
Class inheritance
Think about a feature that lets you use methods like attributes with simple dot syntax.
Direct method calls
Think about a feature that lets you use methods like attributes with simple dot syntax.
1
What two decorators are used to create getters and setters for a property?
@getter and @setter
Think about decorators that allow method calls to use simple dot notation without parentheses.
@attr.get and @attr.set
Think about decorators that allow method calls to use simple dot notation without parentheses.
@compute and @assign
Think about decorators that allow method calls to use simple dot notation without parentheses.
@property and @<property_name>.setter
4