Transcript
How can you customize what happens when you assign to a specific attribute on a Python class?
Accessing and updating attributes on a class
Here we have a class called Person
:
class Person:
def __init__(self, name, location):
self.name = name
self.location = location
Person
objects have a name
attribute and a location
attribute:
>>> trey = Person("Trey", "San Diego")
>>> trey.name
'Trey'
>>> trey.location
'San Diego'
We want to make it so that whenever we assign to the location
attribute all previous values of the location
attribute would be stored somewhere.
>>> trey.location = "Portland"
We'd like to make a past_locations
attribute which would show us all previous values for location
on a specific Person
object:
>>> trey.past_locations
Getter and setter methods (not recommended)
In many programming languages, like Java, the solution to this problem is getter methods and setter methods.
So instead of having a location
attribute, we would have a private attribute. Python doesn't have private attributes, but sometimes we put an underscore (_
) before an attribute name to note that it's private by convention.
Here's an updated version of our Person
class with a getter and setter method and a private (by convention) _location
attribute:
class Person:
def __init__(self, name, location):
self.name = name
self.past_locations = []
self.set_location(location)
def get_location(self):
return self._location
def set_location(self, location):
self._location = location
self.past_locations.append(location)
On this new class, instead of accessing location
directly (which doesn't work anymore) we would call the get_location
method:
>>> trey = Person("Trey", "San Diego")
>>> trey.get_location()
'San Diego'
And to set a location we'd call the set_location
method:
>>> trey.set_location("Portland")
>>> trey.get_location()
'Portland'
The benefit of getter and setter methods is that we can hook into these methods, putting any code we'd like inside them.
For example in our set_location
method, we're appending to the past_locations
list:
def set_location(self, location):
self._location = location
self.past_locations.append(location)
So that past_locations
attribute now shows all locations we've ever set for this Person
object:
>>> trey.past_locations
['San Diego', 'Portland']
Using properties to hook into assignment of an attribute
In Python, we don't tend to use getter and setter methods. We don't have a get_name
, and a set_name
, and a get_location
, and a set_location
for every single attribute. Instead, we tend to just assign the attributes and read from attributes as we like.
But in this particular situation (where we have an attribute and we'd like to change how it works) we're kind of stuck. We need some way to hook into the assignment of that attribute. Fortunately, in Python, there is a way to do this: we can use a property.
Here's our modified Person
class with properties:
class Person:
def __init__(self, name, location):
self.name = name
self.past_locations = []
self.location = location
@property
def location(self):
return self._location
@location.setter
def location(self, location):
self._location = location
self.past_locations.append(location)
Properties allow us to hook into the getting of an attribute.
>>> trey = Person("Trey", "San Diego")
>>> trey.location
'San Diego'
When we access the location
attribute now, under the hood it's actually accessing the _location
attribute.
But properties also allow us to customize what happens when we assign to a specific attribute.
By default, if we assign to a property we'll get an error. But in our case, we don't get an error:
>>> trey.location = "Portland"
>>> trey.location
'Portland'
It works because we've implemented a setter for our property:
@location.setter
def location(self, location):
self._location = location
self.past_locations.append(location)
As you can see, the syntax for property setters is a little weird. So rather than memorizing the syntax, you can just look it up when you need it.
The syntax for property setters is a little bit weird, so I don't recommend memorizing it (just look it up when/if you need it).
Breaking down the syntax for property setters
The property setter syntax starts with the name or our property (location
) followed by .setter
. We use that as a decorator to decorate a location
method (named the same as our property):
@location.setter
def location(self, location):
...
We accept an argument which represents whatever is assigned to this property (on the right-hand side of the equals sign during assignment).
We're then storing that actual value on _location
, and every time our location
changes, we're appending each value to our past_location
list:
self._location = location
self.past_locations.append(location)
We never assign to our private attribute directly
So when we access the past_locations
list, both of our locations are reflected:
>>> trey.past_locations
['San Diego', 'Portland']
Any time the location
changes, past_locations
will be updated.
Notice that even in our initializer, we're assigning to location
(rather than _location
):
def __init__(self, name, location):
self.name = name
self.past_locations = []
self.location = location
That's the reason "San Diego" is the first value in this past_locations list
: when we assigned to the location
attribute in our initializer it called our property setter.
In fact, every time location
is assigned to, the setter will be called for this property.
Summary
If you would like to customize what happens when you assign to a specific attribute on your class in Python, you can use a property with a setter.
from Planet Python
via read more
No comments:
Post a Comment