8

Getters and Setters: Manage Attributes in Python

 1 year ago
source link: https://realpython.com/python-getter-setter/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Getting to Know Getter and Setter Methods

When you define a class in object-oriented programming (OOP), you’ll likely end up with some instance and class attributes. These attributes are just variables that you can access through the instance, the class, or both.

Attributes hold the internal state of objects. In many cases, you’ll need to access and mutate this state, which involves accessing and mutating the attributes. Typically, you’ll have at least two ways to access and mutate attributes. You can either:

  1. Access and mutate the attribute directly
  2. Use methods to access and mutate the attribute

If you expose the attributes of a class to your users, then those attributes automatically become part of the class’s public API. They’ll be public attributes, which means that your users will directly access and mutate the attributes in their code.

Having an attribute that’s part of a class’s API will become a problem if you need to change the internal implementation of the attribute itself. A clear example of this issue is when you want to turn a stored attribute into a computed one. A stored attribute will immediately respond to access and mutation operations by just retrieving and storing data, while a computed attribute will run computations before such operations.

The problem with regular attributes is that they can’t have an internal implementation because they’re just variables. So, changing an attribute’s internal implementation will require converting the attribute into a method, which will probably break your users’ code. Why? Because they’ll have to change attribute access and mutation operations into method calls throughout their codebase if they want the code to continue working.

To deal with this kind of issue, some programming languages, like Java and C++, require you to provide methods for manipulating the attributes of your classes. These methods are commonly known as getter and setter methods. You can also find them referred to as accessor and mutator methods.

What Are Getter and Setter Methods?

Getter and setter methods are quite popular in many object-oriented programming languages. So, it’s pretty likely that you’ve heard about them already. As a rough definition, you can say that getters and setters are:

  • Getter: A method that allows you to access an attribute in a given class
  • Setter: A method that allows you to set or mutate the value of an attribute in a class

In OOP, the getter and setter pattern suggests that public attributes should be used only when you’re sure that no one will ever need to attach behavior to them. If an attribute is likely to change its internal implementation, then you should use getter and setter methods.

Implementing the getter and setter pattern requires:

  1. Making your attributes non-public
  2. Writing getter and setter methods for each attribute

For example, say that you need to write a Label class with text and font attributes. If you were to use getter and setter methods to manage these attributes, then you’d write the class like in the following code:

# label.py

class Label:
    def __init__(self, text, font):
        self._text = text
        self._font = font

    def get_text(self):
        return self._text

    def set_text(self, value):
        self._text = value

    def get_font(self):
        return self._font

    def set_font(self, value):
        self._font = value

In this example, the constructor of Label takes two arguments, text and font. These arguments are stored in the ._text and ._font non-public instance attributes, respectively.

Then you define getter and setter methods for both attributes. Typically, getter methods return the target attribute’s value, while setter methods take a new value and assign it to the underlying attribute.

Note: Python doesn’t have the notion of access modifiers, such as private, protected, and public, to restrict access to attributes and methods in a class. In Python, the distinction is between public and non-public class members.

If you want to signal that a given attribute or method is non-public, then you should use the well-established Python convention of prefixing the name with an underscore (_).

Note that this is just a convention. It doesn’t stop you and other programmers from accessing the attributes using dot notation, as in obj._attr. However, it’s bad practice to violate this convention.

You can use your Label class like in the examples below:

>>> from label import Label

>>> label = Label("Fruits", "JetBrains Mono NL")
>>> label.get_text()
'Fruits'

>>> label.set_text("Vegetables")

>>> label.get_text()
'Vegetables'

>>> label.get_font()
'JetBrains Mono NL'

Label hides its attributes from public access and exposes getter and setter methods instead. You can use these methods when you need to access or mutate the class’s attributes, which are non-public and therefore not part of the class API, as you already know.

Where Do Getter and Setter Methods Come From?

To understand where getter and setter methods come from, get back to the Label example and say that you want to automatically store the label’s text in uppercase letters. Unfortunately, you can’t simply add this behavior to a regular attribute like .text. You can only add behavior through methods, but converting a public attribute into a method will introduce a breaking change in your API.

So, what can you do? Well, in Python, you’ll most likely use a property, as you’ll learn soon. However, programming languages like Java and C++ don’t support property-like constructs, or their properties aren’t quite like Python properties.

That’s why these languages encourage you never to expose your attributes as part of your public APIs. Instead, you must provide getter and setter methods, which offer a quick way to change the internal implementation of your attributes without changing your public API.

Encapsulation is another fundamental topic related to the origin of getter and setter methods. Essentially, this principle refers to bundling data with the methods that operate on that data. This way, access and mutation operations will be done through methods exclusively.

The principle also has to do with restricting direct access to an object’s attributes, which will prevent exposing implementation details or violating state invariance.

To provide Label with the newly required functionality in Java or C++, you must use getter and setter methods from the beginning. How can you apply the getter and setter pattern to solve the problem in Python?

Consider the following version of Label:

# label.py

class Label:
    def __init__(self, text, font):
        self.set_text(text)
        self.font = font

    def get_text(self):
        return self._text

    def set_text(self, value):
        self._text = value.upper()  # Attached behavior

In this updated version of Label, you provide getter and setter methods for the label’s text. The attribute holding the text is non-public because it has a leading underscore on its name, ._text. The setter method does the input transformation, converting the text into uppercase letters.

Now you can use your Label class like in the following code snippet:

>>> from label import Label

>>> label = Label("Fruits", "JetBrains Mono NL")
>>> label.get_text()
'FRUITS'

>>> label.set_text("Vegetables")
>>> label.get_text()
'VEGETABLES'

Cool! You’ve successfully added the required behavior to your label’s text attribute. Now your setter method has a true goal instead of just assigning a new value to the target attribute. It has the goal of adding extra behavior to the ._text attribute.

Even though the getter and setter pattern is quite common in other programming languages, that’s not the case in Python.

Adding getter and setter methods to your classes can considerably increase the number of lines in your code. Getters and setters also follow a repetitive and boring pattern that’ll require extra time to complete. This pattern can be error-prone and tedious. You’ll also find that the immediate functionality gained from all this additional code is often zero.

All this sounds like something that Python developers wouldn’t want to do in their code. In Python, you’ll probably write the Label class like in the following snippet:

>>> class Label:
...     def __init__(self, text, font):
...         self.text = text
...         self.font = font
...

Here, .text, and .font are public attributes and are exposed as part of the class’s API. This means that your users can and will change their value whenever they like:

>>> label = Label("Fruits", "JetBrains Mono NL")
>>> label.text
'Fruits'

>>> # Later...
>>> label.text = "Vegetables"
>>> label.text
'Vegetables'

Exposing attributes like .text and .font is common practice in Python. So, your users will directly access and mutate this kind of attribute in their code.

Making your attributes public, like in the above example, is a common practice in Python. In these cases, switching to getters and setters will introduce breaking changes. So, how do you deal with situations that require adding behavior to your attributes? The Pythonic way to do this is to replace attributes with properties.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK