Locked learning resources

Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

Locked learning resources

This lesson is for members only. Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

Replacing Getters and Setters With More Advanced Tools

00:00 Using More Advanced Tools. Up to this point, you’ve learned how to create bare-bones getter and setter methods to manage the attributes of your classes. You’ve also learned that properties are the Pythonic way to approach the problem of adding functional behavior to existing attributes.

00:16 In this part of the course, you’ll learn about other tools and techniques that you can use to replace getter and setter methods in Python. Descriptors are an advanced Python feature that allow you to create attributes with attached behaviors in your classes.

00:30 To create a descriptor, you need to use the descriptor protocol, particularly the .__get__() and .__set__() special methods. Descriptors are pretty similar to properties.

00:41 In fact, a property is a special type of descriptor. However, regular descriptors are more powerful than properties and can be reused through different classes.

00:52 To illustrate how to use descriptors to create attributes with functional behavior, let’s say you need to continue developing your Employee class.

01:00 This time, you need an attribute to store the date on which an employee started to work for the company.

01:15 In this update, you added another property to Employee. This new property will allow you to manage the start date of each employee.

01:31 Again, the setter method converts the date from a string to a date object.

01:44 The class works as expected. However, it does start to look repetitive and boring, so you decide to refactor the class. You notice that you are doing the same operation in both date-related attributes, and you think of using a descriptor to pack the repetitive functionality.

02:08 In this update, you create a Date descriptor to manage date-related attributes. The descriptor has a .__set_name__() method that automatically stores the attribute name.

02:20 It also has .__get__() and .__set__() methods that work as the attribute’s getter and setter, respectively.

02:38 birth_date and start_date class variables are defined, and then the class is initialized in the familiar manner.

03:02 ._name is then defined as a property, as seen earlier in the course.

03:13 The two implementations of Employee work similarly. This code is cleaner and less repetitive than the previous version. Go ahead and give them a try.

03:26 In general, if you find yourself cluttering up your classes with similar property definitions, then you should consider using a descriptor instead. Another way to replace traditional getter and setter methods in Python is to use the .__setattr__() and .__getattr__() special methods to manage your attributes.

03:45 Consider the following example, which defines a Point class. The class automatically converts the input coordinates into floating-point numbers.

04:01 The initializer of Point takes two coordinates, x and y. The .__getattr__() method returns the coordinate represented by name.

04:12 To do this, the method uses the instance namespace dictionary, .__dict__. Note that the attribute’s final name will have an underscore preceding whatever you pass in name. Python automatically calls .__getattr__() whenever you access an attribute of Point using dot notation.

04:30 The .__setattr__() method adds or updates attributes. In this example, .__setattr__() operates on each coordinate and converts it into a floating-point number using the built-in float() function. Again, Python calls .__setattr__() whenever you run an assignment operation on any attribute of the containing class. On-screen, you can see how the class works in practice.

05:04 The Point class automatically converts coordinate values into floating-point numbers. You can access the coordinates, x and y, as you would any other regular attribute.

05:14 However, access and mutation operations go through .__getattr__() and .__setattr__(), respectively. Note that Point allows you to access coordinates as public attributes.

05:25 However, it stores them as non-public attributes. You can confirm this behavior with the built-in dir() function.

05:42 The example in this section is a little bit unusual, and you probably won’t use something similar in your own code. However, the tools that you’ve used in the example allow you to perform validations or transformations on attribute access and mutation, just like getter and setter methods do. In a sense, .__getattr__() and .__setattr__() are a generic implementation of the getter and setter pattern. Under the hood, these methods work as getters and setters that support regular attribute access and mutation in Python. In the next section of the course, you’ll take a look at some of the factors you should think about when deciding which of the techniques you’ve seen in this course will be used in your code.

Avatar image for Andras

Andras on March 2, 2025

Hi Darren,

I like the comparison between the Descriptor mechanism and the __getattr__ and __setattr__ mechanism. Honestly, I don’t yet understand the Descriptor mechanism. You could have spent some time on explaining what actually happens in the assignment self.birt_date = birth_date once you defined birth_date as a Date instance implementing the descriptor protocol. That is the key here. But I know there are separate RP courses and tutorials on that so I didn’t care much.

My main question for this lesson is that in the __getattr__ case both atrributes are non-public: _x, _y. So how do users of the Point class know how to use it? Of course this is a trivial class, but it could be way more complicated and if users only see non-public attributes returned from a dir(point) command than it is not so helpful. I guess they would have to read and understand the whole class definition, right?

thanks

Avatar image for Bartosz Zaczyński

Bartosz Zaczyński RP Team on March 3, 2025

@Andras To be completely frank, using .__getattr__() and .__setattr__() this way is a bit hacky. Darren admits that in his summary, saying that it was only for illustration purposes, and you wouldn’t typically use it in your own code. To make your public attributes discoverable through the dir() function, you’d need to specify the corresponding properties or descriptors.

Become a Member to join the conversation.