After using C++ or other languages you can know how to define getters and setters. But if you start learning the first language Python, you can see that they are not so popular. Often people use them for attributes for classes. They help in creating access for private attributes while preserving encapsulation. But in Python you usually use them to uncover attributes partially in public API and use things when you need attributes with useful actions.
Although it is in Pythonic style to use properties, they can anyway have disadvantages. That’s why people start using getters and setters Python more than properties. And also you must know some OOP or in long: object-oriented programming.
After specifying the class in object-oriented programming, usually you use class attributes and instances. And the attributes that you have created are variables that you can use using instances or the class or both.
Firstly, let’s try to create a class in Python.
How to Create Class
If you need to create the class first you need to use the class name and variables. Here is an example:
class Example: first = "Hello" second = "Kodershop"
Here we have created the class with the name Example. Let’s create objects from this class:
class Example: first = "Hello" second = "Kodershop" Example1 = Example() print(Example1.first, Example1.second, "!") #Output: #Hello Kodershop !
So here the object was created which is called Example1 and it is from the Example class.
That all means that when we printed Example1.first, we had got the value that was stored in a variable in the class that we had created. This calling and these variables are our attributes of the class.
Python Class Attributes
So the basic getter definition of attributes in class is that variables in class can be inherited by objects in the class. The next examples will show how it looks in code, but the only thing we must say is that those attributes are written outside the function __init__().
Let’s see the example:
class Example: office = "Dear Kodershop!" def __init__(self, name, position): self.name = name self.position = position Example1 = Example("Steve", "CEO") Example2 = Example("Alex", "Co-founder") print(Example1.name, "is the best worker, but", Example2.name, "is the best blog post employee!") #Output: #Steve is the best worker, but Alex is the best blog post employee!
So here we created the variable called ”office” in the class called Example. Then we used ”init” to create two more variables that are “name” and “position”. “Self” parameter inside init helped us to initialize them.
Python Setter Getter Strategies
So what are a getter and setter? Subsequently, the nation of the objects is held in attributes. In case you want to get admission to this kingdom, you need to have an entry to these attributes. So to get entry to those attributes you can use techniques or access them without delay.
After revealing them to different users, these attributes become part of the elegance’s API. So public attributes imply that other customers can have entry to the attributes in their snippets of codes.
Whilst you are in this example, languages like C++ want to use some techniques to address attributes in lessons. And as you can determine, they may be setters and getters.
Strategies of getter and setter Python have become properly appreciated in programming languages. Human beings use them for item-oriented programming, so surely there are a lot of human beings who’ve already heard about them.
So what are getters setters? If we need to talk about the usage of easy definitions, we are able to say that getter is the method that allows you to have an opportunity to have access to the values in a characteristic. And setter is the method that helps you to change or insert the cost inside the attribute.
Our everyday techniques submit that attrs which are public are operated when you have decided that nobody from builders will ever contact them. While you need to change the inner implementation of the attribute, you will use setter getter Python methods. To enforce the setter and getter it’s required to make the attributes non-public and write each characteristic with setter and getter. For example, say that you want to jot down a Room magnificence with width, length, and height attributes. Permit’s use getters and setters:
For instance, say that you need to jot down a python class getter setter with width, length, and height attributes. Let’s use getters and setters:
class Room: def __init__(self, width, length, height): self._width = width self._length = length self._height = height def get_width(self): return self._width def set_width(self, value): self._width = value def get_length(self): return self._length def set_length(self, value): self._length = value def get_height(self): return self._height def set_height(self, value): self._height = value
Here we can see that the constructor of our class uses three arguments that are width, height, and length. And we saved them in “._width”, “._length”, “._height” non-public attributes.
Often, you can use getter Python methods for returning the focused value of an attribute. And setter methods you use to make a new value and change it in this attribute.
Also, when you are used to programming C++ you can see that Python does not have private, protected, or public modifiers. Python has only public or non-public.
When you want to mark the attribute as non-public, you can use Python in-built syntax that is written before the name. It looks like the underscore “_”. This is only syntax, so it does not ruin all your plans of using dot notation like “object._attribute”. Dot notation is used often in “self.name” or anything else.
Also to use your code and make it work you can use “import”:
class Room: def __init__(self, width, length, height): self._width = width self._length = length self._height = height def get_width(self): return self._width def set_width(self, value): self._width = value def get_length(self): return self._length def set_length(self, value): self._length = value def get_height(self): return self._height def set_height(self, value): self._height = value room = Room(15, 16, 13) print("Width:", room.get_width()) room.set_width(25) print("New width:", room.get_width()) print("Length:", room.get_length()) #Output: #Width: 15 #New width: 25 #Length: 16
Let’s explain what we have done here. As we know the Pythonic way to add behavior for attributes is to make them a property. Properties percent together strategies for getting, setting, deleting, and documenting the underlying data. In short, properties we can call as attributes that have additional behavior.
Actually, it’s not a problem to use properties in the same way as stock attributes. Whilst you get entry to a property, its attached getter method is robotically called. And whilst you mutate the property, its setter technique is called.
The room gives us the opportunity to make instances so we can make changes to the associated height, width, and length.
Features of Setter and Getter
Using a setter and getter only for accessing or changing the values is not the only way. When you want to save all values in uppercase you can also use them. Well, you may use it by attribute ”.text” but you can’t. So if you want to use it, it seems that you have come from Java and C++ because they do not support constructs that are like properties. So that’s the issue why you can not use your attributes here as public. You should use setter and getter and provide implementation without public.
Python properties have plenty of capacity use cases. For instance, we can make properties to form read-write, read-only, and write-only attributes. Properties will let you delete and record the underlying attributes and also more interesting is that you can make them act like controlled attributes with attached behavior without converting the way you figure with them.
Because of Python possibilities when you use properties, people generally tend to use them. So if you need to add some new processing with attributes you can use them. But when you want to change all your attributes, you will waste a lot of time. And even it can overload your code and make it less efficient.
Descriptors in Python
Now after we get to know the base of topics about getter setter Python, also about properties that are written in a Pythonic way and also dealing with problems with how to add more functionality, we can learn other tools and techniques that we may use when we want to change getter and setter in Python on other methods.
So a descriptor is a feature in Python that helps you in creating attributes with behaviors. If you want to create a descriptor, you must use the specified protocol. In other words, it is a syntax that helps you. They are “.__get__()” and “.__set__()” methods.
Factual descriptors are very similar to properties in Python. Or in other words, Python class property setter is a type of descriptor that is more powerful than an in-built property. To clarify a case of how to use descriptors to create attributes with useful behavior, let’s use the example before:
class Room: def __init__(self, width, length, height): self._width = width self._length = length self._height = height @property def width(self): return self._width @width.setter def width(self, value): self._width = value @property def length(self): return self._length @length.setter def length(self, value): self._length = value @property def height(self): return self._height @height.setter def height(self, value): self._height = value
Here we have used “@property” and “@.setter” which allowed us to change the height, width, and length for each room. In general, in case you locate yourself disordering your instructions with comparable property definitions, you ought to not forget to use a descriptor instead.
Methods __setattr__() and __getattr__()
Except traditional getters Python and setters you can use .__setattr__() and .__getattr__() special methods that help you to manage the attributes. Let’s make a class named Position that will change the type of x and y into float:
class Position: def __init__(self, x_value, y_value): self.x_value = x_value self.y_value = y_value def __getattr__(self, firstname: str): return self.__dict__[f"_{firstname}"] def __setattr__(self, firstname, value): self.__dict__[f"_{firstname}"] = float(value)
Here we can see that __init__ takes our position coordinates “.__getattr__()” method returns the firstname that is the coordinate. To make this done we used “.__dict__”. Take note that Python use “.__getattr__()” automatically when you try to access an attribute with dot notation. What about the “.__setattr__()” method is that adds or changes the attributes. For example we used it for operating with each positional coordinate and converting it into float type. And also as with “.__getattr__()”, “.__setattr__()” runs automatically when you use assignment operation.
class Position: def __init__(self, x_value, y_value): self.x_value = x_value self.y_value = y_value def __getattr__(self, firstname: str): return self.__dict__[f"_{firstname}"] def __setattr__(self, firstname, value): self.__dict__[f"_{firstname}"] = float(value) position = Position(21, 42) print("The toy located in:\nx =", position.x_value) print("y =",position.y_value) position.y_value = 84 print("new y =",position.y_value) #Output: #The toy located in: #x = 21.0 #y = 42.0 #new y = 84.0
Here in our class we have to get or set accessors expected with our positions as regular attributes.
So here we used a very interesting example and showed how it works. But actually, you will not use similar. The tools that we have used help you to make validations and transformations with access to our attribute.
And in short, “.__getattr__()’’ and “.__setattr__()” are like an implementation that is like a Python getter and setter pattern. But actually, they work the same as regular methods getters and setters.
What Does Inheritance Look Like?
If you want to inherit some classes in Python you will meet up with one problem. For instance, you want to change the Python property method in a subclass. And if you try to Google it, you do not find any safe ways to do this. In short, it is hard to overturn the regular getter and wait that this property getter and setter will have the same functions as in the parent class. So that means that when you try to change the method in the inherited class you change the whole property including its other methods as a setter. Let’s see the example:
class Goods: def __init__(self, title): self._title = title @property def title(self): return self._title @title.setter def title(self, value): self._title = title class Room(Goods): @property def title(self): return title
Here we can see that in class “Room” we changed the @property. This case shows us that we changed the whole .title property, including example setter too. Let’s add some functions:
class Goods: def __init__(self, title): self._title = title @property def title(self): return self._title @title.setter def title(self, value): self._title = value class Room(Goods): @property def title(self): return super().title banana = Room("banana") print("I want", banana.title) banana.title = "new banana" print("No, I want", banana.title) #Output: #AttributeError: Object does not support property or method
Here we can see how Python can not find the setter of the inherited class. This is because we have changed it before and we are now trying to use a false setter, but he was not inherited. So, what is the solution to this situation?
Let’s use regular get set in Python methods:
class Goods: def __init__(self, title): self._title = title def get_title(self): return self._title def set_title(self, value): self._title = value class Room(Goods): def get_title(self): return super().get_title()
Here we see that no issues were found. Python has an in-built independent get and set in Python methods. Room is a subclass of Goods and it overrides the getter. We can see that this line of code does not change the functionality of setter and successfully it was inherited from the parent class.
Let’s make an example with get set Python and functions:
class Goods: def __init__(self, title): self._title = title def get_title(self): return self._title def set_title(self, value): self._title = valueclass Room(Goods): def get_title(self): return super().get_title() banana = Room("banana") print("I want", banana.get_title()) banana.set_title("new banana") print("No, I want", banana.get_title()) #Output: #I want banana #No, I want new banana
Now our class can work properly. The changed getter method in the inherited class is functional. And the setter method also works well. Well done!
What is Better? Set and Get in Python or Properties?
If you start coding, you know the answer. There are some situations where methods of setter and getter are preferred over properties, but also there are other situations where Pythonic properties play better.
Let’s try to distribute by points why the get and set in Python are dealing with better, we can do this. When we talk about transformations, the methods setter and getter run better on attribute Python accessors and mutation Python. Also, they use inheritance and this is very good. Getter and setter can raise Python exception attributes when they are accessing the attributes. And finally, their integrations are being facilitated in heterogeneous enhancement teams.
When we want to talk about properties in Python, the problem can be seen when you are making a huge amount of lines of code. In other words, take note that you should avoid making slow Python mutations with Python properties. When somebody repeatedly tries to make access to a given attribute, users can suffer from waiting because it can take a long time, and the efficiency of your code can suffer a lot. So when your plans are to use Pythonic properties to change values in attributes, be sure that your mutations are fast, because if you will not do that and use slow get set accessors, you will change your code to use a simple setter and getter.
Another situation is that getter and setter methods are more flexible than you would think. Let’s make an example with the name Goods and its expiration date attribute:
class Goods: def __init__(self, title, expiration_date): self.title = title self._expiration_date = expiration_date def get_expiration_date(self): return self._expiration_date def set_expiration_date(self, value, force=False): if force: self._expiration_date = value else: raise AttributeError("Can't set expiration_date. Have you written it in numbers?")
Our date was a constant value and we made it read-only, so it means non-public. Also because of the fact that human error exists, so in life, you will face a lot of cases where somebody writes letters instead of numbers in the date section. Here we provided regular getter and setter methods for the “.expiration_date” attribute. Here our setter method has taken the “force” argument so that we can be ready for mistakes made by people. Also factually, traditional setters do not use more than one argument in code, so we can say that our code is wrong.
class Goods: def __init__(self, title, expiration_date): self.title = title self._expiration_date = expiration_date def get_expiration_date(self): return self._expiration_date def set_expiration_date(self, value, force=False): if force: self._expiration_date = value else: raise AttributeError("Can't set expiration_date. Have you written it in numbers?") milk = Goods("A packet of milk", "2023-5-29") print("Milk:", milk.title) print("Milk expiration date:", milk.get_expiration_date()) milk.set_expiration_date("2023-6-29") #Output: #Traceback (most recent call last): #AttributeError: Can't set expiration_date. Have you written it in numbers?
Here we have written the code without the “force” argument and got a message. Let’s make it work properly:
class Goods: def __init__(self, title, expiration_date): self.title = title self._expiration_date = expiration_date def get_expiration_date(self): return self._expiration_date def set_expiration_date(self, value, force=False): if force: self._expiration_date = value else: raise AttributeError("Can't set expiration_date. Have you written it in numbers?") milk = Goods("A packet of milk", "2023-5-29") print("Milk:", milk.title) print("Milk expiration date:", milk.get_expiration_date()) milk.set_expiration_date("2023-6-29", force=True) print("New milk expiration date:", milk.get_expiration_date()) #Output: #Milk: A packet of milk #Milk expiration date: 2023-5-29 #New milk expiration date: 2023-6-29
In the first example we tried to change the expiration date by using .set_expiration_date. But the problem was that we did not use force argument – we have not changed its setting to True. That is why we have got an AttributeError. So in the next example we replaced its setting and said to Python that we are sure that there are no mistakes. So it worked.
As we said before, the Python property setter getter does not accept more than one argument in the setter, because it accepts only that variable that you want to change or make.
Usually, you won’t accept a work message like “objects.attributes = value” to heighten an exception. Especially, you may accept messages that heighten exceptions in response to mistakes. Here regular Python getter setter methods were extra specific than properties.
Let`s look like on line: “phone.number = ‘360’”. Undoubtedly, it looks like something that will work in a solid way and will not raise an exception. It is an ordinary attribute. But if we use a setter like here: “phone.set_number(‘360’)”, we can see that it can heighten a message about a value error. “inserted phone number is not a phone number.”. In this case, the setter method is a better way to solve this. It simply expresses the code’s beneficial behavior.
Generally, keep away from raising exceptions from your Python properties. You can do that only if you want to use a property to offer attributes that will be only for reading. If you ever need to heighten messages on access or mutation, then you absolutely shouldn’t forget to use getter and setter methods in place of properties.
So in short, using a regular setter and getter can help in solving troubles when they make their code.
In the end, we can wrap up that we know better what is setter and getter methods. But they are not so popular because Python has in-built properties that allow you to mutate and get access to the attributes. But genuinely, as we have before in some situations they have drawbacks that you can fix only by changing properties in regular Python get and set methods.