How to Work with Python Properties and Decorators
In the last guide we walked through how we could get and set values inside of a python class but if you remember at the very beginning of the guide I mentioned how the practice of simply overriding and having access to all of the data inside of a class in the way that we did it just like how we were able to grab the data right here and how we're able to set it at any time this is considered in some circles a poor practice.
Guide Tasks
  • Read Tutorial
  • Watch Guide Video
  • Complete the Exercise
Video locked
This video is viewable to users with a Bottega Bootcamp license

It's perfectly fine to use in certain circumstances however the issues that can arise usually are ones that are a little bit more advanced and they deal with data encapsulation which means that imagine that you're building out this class and other different programs are going to be using it.

If they accidentally override something that they shouldn't even have access to then it could break the entire program and so that's a reason why simply letting all of your data elements be wide open in all circumstances can be bad. Now I will add that there are plenty of times where being able to simply access these values works perfectly fine. Usually when you run into the issue that I'm referring to and what I'm going to show you how you can fix that usually happens when you're building out very large systems and you're building helper modules that other people are going to be using and that's when you really have to be very careful.

But for right now what I'm going to show you is how to do this and use what is called the property decorator. So we're going to be able to fix the whole issue where everyone has access to all of the data. And so we may have a situation where we want to pick and choose and say okay some of the data should be able to be accessible just like we have it right here

large

Then we want to protect some elements so we may not want the client or the total to be able to be overridden. And so what I'm going to do is I'm gonna get rid of our print statements here at the bottom just so we don't get things mixed up and now I'm going to come up to the top and start building out these properties. But before I do that one thing I want to show and talk about is a standard convention in the Python community any time that you're dealing with data that you want to have either private or protected.

And I'll talk about what that means whenever you're doing that you want to add underscores in front of it. So if you want something protected and what that means is that anything in the class you want to have access to and any child classes. So when we start to talk about inheritance so say that we have invoice right here. And later on, we're going to talk about how we can create a subclass called maybe special invoice and it's going to inherit from invoice and don't worry if none of that makes sense just know that that's a very popular process in the object-oriented programming community where you can have parent-child relationships with classes.

And so what protected means is that the child classes will have access to this data. And so whenever you want to do that just put an underscore right in front of it. It's very important to note that there is nothing special at all about the underscore your code is going to work exactly the same way if you have it there or not. The underscore is a clue to you and to future developers working on your program that this data attribute should be protected.

Now if you have a data attribute that you want to have that should only be able to be accessed inside of this one class, not even child classes that are called a private attribute. And the common convention for using that is two underscores before the variable name and we're not going to use the private one here I'm just going to use the protected because that's one of the more common ones that you're going to be using in your programs.

I'm gonna make both of these protected I'm going to add an underscore before client and before total and so that means that if I come down to this formatter that I'm going after do the same thing here for self.client and self.total and they have now their own set of the correct calls.

large

Now let's just verify that everything is working properly so I'll say print(google.formatter) and we shouldn't have any change whatsoever everything should still work. If I run that you can see everything there is still working properly. Google owes one hundred dollars so that works.

large

But now let's talk about how we can use properties. So if I come back into the class I'm going to use an @ symbol followed by the name property. And so this is going to do this is called a decorator and what it essentially does is it decorates it wraps around the property that we're wanting to work with. So the syntax for doing that is you're going to create a function now called client and notice how this is mapped pretty much right here. And this is the standard convention it's not underscore client its actually just the word client and then you pass in self.

And then inside of here you simply say return self._client. And so now what you can do is you can come down here and we don't need this a matter anymore let's just print out our Google client and let's run it again and that is working properly.

large

Now if your first question is why in the world would we need to do that? Because technically everything was already working. Well, now what we've done is we have given a clear distinction with how we've written our code on how you should treat this invoice.

So whenever I'm looking at python code and I see that there is a property decorator here and then I see what's underneath it. This tells me that I will most likely want to access what is here at some point. So that means I have access to directly call on client and then get that data back. If I come to a class either one I've written or that someone else has written and I just see a bunch of these self dot underscore items and then I see clients and totals but I don't see any property decorator's.

What that tells me is that all of this data here is simply for the internal usage of the class and that I should not call on those directly. And so whenever we're writing code we're not just writing code that works we're also writing code that communicates the story on how our programs should be run and how others should use our code that's just as important as making sure that our programs work properly.

So a lot of the common conventions that you see me doing and also that you'll find in the Python community are just as much about code readability as they are about performance or anything related to that.

So now with all of that in place, we have our property here and let's come and let's do the same thing for our total. So I'm going to come here say total and this is going to return total and we'll hit save. And so everything here is working properly. So if I come here and I want to print out the total hit save, run it again you can see we have access to Google and the hundred.

large

So far we've added two properties and these take care of our getters so this allows us to get the values of client and total.

Now let's talk about our scatter values. So let's imagine a scenario where we don't want to give the ability to override the total and so that is I think a pretty common use case for that. And so we're not going to create a setter for total but we are for clients so we want to give the ability to override the client value and the way we can do that is not with a property decorator but instead, we're going to say @ and then whatever the name is of the value that we want the ability to override.

So I'm gonna say @ client because that is what I'm wanting to override this client value right here I'm gonna say @ client dot setter and then inside of here, I'm going to create a new function. So I'll say def client and then pass in self, pass in client and now I'm not going to return anything. I'm simply going to override the value in the class so I'll say self._client = client.

And now if I save this I can come down here and keep my client value and then let me override it right below. So say Google dot client and now we can set this equal to anything we want. And then I'll copy this and put it down below and so now if everything's working and what we should have is it should print out Google for right here it should override with Yahoo. And then it should print out Yahoo. So save it come over here run it one more time and that is working perfectly.

large

So that is how you can be very explicit with your getters and setters in Python classes.

Code

class Invoice:

    def __init__(self, client, total):
        self._client = client
        self._total = total

    def formatter(self):
        return f'{self._client} owes: ${self._total}'

    @property
    def client(self):
        return self._client

    @client.setter
    def client(self, client):
        self._client = client

    @property
    def total(self):
        return self._total

google = Invoice('Google', 100)

print(google.client)

google.client = 'Yahoo'

print(google.client)