November Black Friday C# week
Black friday is here! Get up to 80 % extra points for free! More info
Only this week up to 80 % off on C# courses. More info

Lesson 10 - Properties in Python

In the previous lesson, Static class members in Python, we learned about static class members in Python. In today's tutorial, we're going to look at the other class members that we haven't gone over yet.

Properties

We often want to have control over how an object attribute is being changed from outside of the class. We'd like to set the attribute as read-only or react to its changes somehow. Let's create a new project (name it properties) and add the following Student class which will represent a student in a database:

class Student:

    def __init__(self, name, gender, age):
        self.name = name
        self.male = gender
        self.age = age
        self.full_aged = (age > 18)

    def __str__(self):
        i_am_full_aged = "I'm" if self.full_aged else "I'm not"
        gender = "male" if self.male else "female"
        return "I'm {0}, {1}. I'm {2} years old and {3} of age.".format(
            self.name, gender, self.age, i_am_full_aged)

The class is very simple, the student has a name, gender, and age. The full_aged attribute is set according to the age and provides a more comfortable determination whether the student is of age from different places in the system. We use a Boolean value to store the gender, True indicates that he's male. The constructor will determine whether the student is of age or not. The __str__() method has been altered to suit our needs. In a real world situation, it'd probably just return the student's name. Let's create a student using the constructor:

s = Student("Peter Brown", True, 20)
print(s)
input()

The output:

Console application
I'm Peter Brown, male. I'm 20 years old and I am of age.

Everything looks nice, but the attributes can be re-written. We can break the object like this, so it'll no longer function properly (i.e. would have an inconsistent internal state):

s = Student("Peter Brown", True, 20)
s.age = 15
s.male = False
print(s)
input()

The output:

Console application
I am Peter Brown, female. I am 15 years old and I am of age.

Certainly, we would want full_aged to be updated when the age is changed. Aside from that, no other attribute would need to be altered externally. Students don't usually change their genders or names. However, we want to keep the properties accessible for reading, so we can't make them private. In earlier lessons of our course, we've used get methods to read private attributes. We would name them something like get_age() and so on. We'll create get methods to be able to read certain attributes and make these attributes private to prevent them from being modified from the outside. The class would now look something like this (I omitted the constructor and __str__()):

class Student:


    def __init__(self, name, gender, age):
        self.__name = name
        self.__male = gender
        self.__age = age
        self.__full_aged = (age > 18)

    def __str__(self):
        i_am_full_aged = "I'm" if self.__full_aged else "I'm not"
        gender = "male" if self.__male else "female"
        return "I'm {0}, {1}. I'm {2} years old and {3} of age.".format(
            self.__name, gender, self.__age, i_am_full_aged)

    def get_name(self):
        return self.__name

    def is_full_aged(self):
        return self.__full_aged

    def get_age(self):
        return self.__age

    def male(self):
        return self.__male

    def set_age(self, value):
        self.__age = value
        self.__full_aged = True
        if age < 18:
            self.__full_aged = False

The methods just returning a value are very simple. In the method setting the age is some more logic, since we have to reconsider the full_aged attribute. We made sure we can't set variables in any other way than what we want. We now control all the attribute changes and can work with them as needed. Our design must prevent all unwanted changes of the internal state that would cause an object to malfunction.

Methods for returning values are called getters and methods for setting values are called setters. We could potentially add an edit_student() method to edit the other attributes sort of like the constructor. Essentially, the student's name, age, and other attributes would be updated using this method. We could also validate the values being set there since we would be able to handle all attempts to change certain values in one place. Implementing getters and setters manually is without a doubt hard work. Isn't there any better way? Yep, there's shorter syntax in Python for us! In that case, we're no longer talking about attributes, rather properties.

The property syntax is very similar to the method syntax:

@property
def name(self):
    return self.__name # the attribute bound to the method

We create a method that has the same name as the desired property name. In the method body, we return the attribute associated with the property. We decorate the method with the @property decorator. This makes the method a property. Decorators have the following syntax:

@decorator_name
def some_method(self):
     ...

In fact, decorators are callable objects (for example, functions, methods, or objects with the __call__() method) that return a modified version of a method or function. Therefore, the following syntax can also be used:

original_method_name = decorator_name()

In Python, we have a private attribute, and we have two methods for it that Python calls based on the context (depending on the situation, whether we're reading or writing). If we don't add a setter method to the property, it can't be changed from within nor from outside. We then use the property as it was a regular attribute:

print(my_object.name) # reading
my_object.name = "John Black" # writing

If we wish the property to be writable, not just readable, we could define a setter as well. Let's demonstrate it on our example of the full-aged problem which must be re-evaluated when the student's age changes:

...
@property
def age(self):
    return self.__age

@age.setter
def age(self, value):
    self.__age = value
    self.__full_aged = (value > 18)

First and foremost, we'll have to create a private __age attribute, the value will be stored there. We'll work with this attribute in the getter and the setter. In the setter, we'll use another parameter for the value to be assigned. We'll treat the age as we would treat an attribute from now. Re-assignment of the age triggers the internal logic to update the full_aged attribute:

s.age = 15 # the full_aged attribute will update immediately as well

Likewise, we could implement a custom getter and log something:

@property_name.getter
def property_name(self):
    return private_property_attribute

If we wanted the getter to act differently, we would modify the method body. However, getter always has to return something, otherwise it would be no getter :)

__dict__ and __slots__

When declaring properties, we can use either a private attribute (see above) to store the value into or a public attribute as well. However, if we used a public attribute, the attribute and the property method names would be the same and the program would fall into recursion. But it's possible to work this around. All object attributes are kept in the dictionary associated with the object. We'll get to it like this:

object_name.__dict__

Then we can read/assign the value without recursion:

object_name.__dict__["name_name"] # read
object_name.__dict__["name_name"] = value # write

However, if there is no valid reason for the property attribute to be public, it is unnecessary. Moreover, there is one pitfall. In this way, the values ​​can be changed from the outside without being checked. We can sanitize it with __slots__, which should not be used at all. According to Python documentation, we should use __slots__ only if we create a large number of instances of a class and want to save memory at all costs.

Nevertheless, even private attributes can be accessed with the help of name mangling - we'd get the attribute using the following syntax:

my_object.__ClassName_attribute_name

But this is certainly not getting / changing a private attribute by mistake :) In the next lesson, Magic Methods in Python, we'll look at the object magic methods.


 

Download

Downloaded 0x (1.52 kB)
Application includes source codes in language Python

 

 

Article has been written for you by David Capka
Avatar
Do you like this article?
No one has rated this quite yet, be the first one!
The author is a programmer, who likes web technologies and being the lead/chief article writer at ICT.social. He shares his knowledge with the community and is always looking to improve. He believes that anyone can do what they set their mind to.
Unicorn College The author learned IT at the Unicorn College - a prestigious college providing education on IT and economics.
Previous article
Static class members in Python
All articles in this section
Object-Oriented Programming in Python
Thumbnail
Next article
Magic Methods in Python
Activities (3)

 

 

Comments

To maintain the quality of discussion, we only allow registered members to comment. Sign in. If you're new, Sign up, it's free.

No one has commented yet - be the first!