Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / Python

Python vs C++ Series: Getter, Setter, and Property

4.78/5 (8 votes)
26 Sep 2021CPOL6 min read 9K  
Introduce Python Property from the concept of C++ Getters and Setters
In this post, I will briefly review encapsulation, followed by a discussion of access functions in C++ and access control and property in Python.

As a professional C++ programmer since C++03, the C++ way object-oriented thinking has been deeply embedded in my mind, and it helped me a lot when I picked up a new language such as C# and Java. However, the benefit was not apparent when I encountered Python the first time. Python is also an object-oriented programming language but significantly differs from other object-oriented programming languages like C++ and Java. Therefore, this series tries to point out some noteworthy Pythonic programming that may surprise C++ programmers, and it is not a Python tutorial. I hope my experience could help people who know C++ to pick up Python even simpler.

The first article of the Python vs. C++ Series starts from one basic object-oriented programming concept – encapsulation and access functions.

Note that the Python code in the series assumes Python 3.7 or newer.

Brief Review of Encapsulation

Encapsulation is an object-oriented programming concept which encloses the implementation detail of a class. Why not disclose the implementation detail? One good programming practice is that a client code should only access class’ public interfaces. As long as the interface remains the same, the client code does not need to change if the implementation changes. Encapsulation also reduces the complexity and simplifies debugging processes. Besides, an encapsulated class helps protect the data and prevent misuse because a client code cannot access the protected portion of a class.

What Should Be Protected? How to Access if Necessary?

In addition to the implementation details, data members, in general, are also the details that should be protected. However, it may be appropriate to provide a public interface allowing a client code to access the data members in the class’s context. This type of interface is usually called an access function. Access functions typically come in two flavors: getter and setter. A getter is a method that is called when we access a data member for reading. In contrast to getter, a setter is a method called to modify a data member.

Benefits of Access Functions

Besides providing the accessibility of data members, access functions offer other benefits. For instance, a setter can perform some operations before return, and a getter can return value in a friendly format. We can add checks logic for getters to verify that the input value is valid before updating the data member.

Access Functions in C++

The access restriction to C++ class members is labeled by the access specifiers – public, private and protected – within the class body. A class member in the public section is accessible publicly. Only the class self can access the members inside the private section. A protected member can be used by the class self and its derived class.

The following example demonstrates the basic idea of getters and setters in C++.

C++
class MyClass
{
    public:
        int getMyData() {
            return myData;
        }

        void setMyData(int value) {
            myData = value;
        }

    private:
        int myData = 0;
};

Access Control and Property in Python

Python does not use access specifiers to restrict access to a class. In fact, Python does not have a mechanism to prevent a client code from accessing private members. Everything is accessible in Python; everything is public. Note that the term private is usually not used in Python programming since no attribute is really private in Python. The term internal is used instead to indicate an attribute is supported to be used internally.

Although Python does not restrict any access, it does not mean encapsulation is no longer critical to Python. It just means Python has a different approach to support data encapsulation.

Naming Convention

Python programming relies on naming conventions to establish a contract between the code owner and users. If a class member is internal, its name starts with a single underscore; otherwise, it’s public. For example:

Python
class MyClass:

    def __init__(self) -> None:
        # this variable starts with an underscore (_),
        # it indicates it is supposed to be used internally only
        self._my_data: int = 0

    def get_my_data(self) -> int:
        return self._my_data

    def set_my_data(self, value: int) -> None:
        self._my_data = value

    # A method starts with an underscore (_), also means private.
    def _private_method(self) -> None:
        pass

When a Python programmer sees a class member named with a leading underscore, they will know it is intended to be used internally and not accessed by external code.

However, a client code can still access the internal member if the client really wants to do so. The following code is totally valid.

Python
my_class = MyClass()
my_class._my_data = 10

(The sample code is available at Github.)

Name Mangling

In addition to prefix with a single underscore, double leading underscores also means private. However, their behavior is slightly different. The difference is name mangling – when a class member is named with a double leading underscore (e.g., __my_member), name mangling is invoked, and its name is replaced with a name that includes an underscore and the class name before the actual name, like _ClassName__my_member.

Name mangling makes it harder to access an internal member. However, a client code can still access an internal member with double leading underscores via its mangling name. See the example below:

Python
class MyClass:
    def __init__(self) -> None:
        # Name mangling
        self.__my_member = 0
if __name__ == "__main__":
    my_class._MyClass__my_member = 10

Although nothing really prevents access to an internal attribute, the naming convention, at least, tells the client that the attributes are internally used; if you really want to access them, please be careful.

More details about the Python naming convention can be found at PEP8 – Naming Conventions.

Single Leading Underscore or Double Leading Underscore?

Regarding using single leading underscore or double leading underscores to name an internal attribute, we should always prefer a single leading underscore. Using double-leading underscore is discouraging. According to PEP8, “Use one leading underscore only for non-public methods and instance variables. To avoid name clashes with subclasses, use two leading underscores to invoke Python’s name mangling rules.” In addition, a member named with a double underscore prefix also reduces the readability, which may confuse the client with Python Special Method.

Pythonic Way to Do Getters and Setters: Property

Because everything is public in Python, providing access functions such as getter and setter in the same way as C++ does not make sense. But sometimes, we need getters and setters for our internal attributes, not to protect them but to perform some operations or checks before returning the value or update the internal members. The Pythonic way is to use @property – a built-in decorator provided by Python language.

The following code demonstrates how to use @property decorator to implement a getter and a setter.

Python
class Contact:
    def __init__(self, first_name: str, last_name: str) -> None:
        self._first_name = first_name
        self._last_name = last_name
        self._email: Optional[str] = None

    @property
    def name(self) -> str:
        # The @property decorator turns the name() method into a getter
        # for a read-only attribute with the same name, so a client can
        # access it by doing c.name if c is an instance of Contact.
        return f"{self._first_name} {self._last_name}"

    @property
    def email(self) -> str:
        # The @property docorator turns the email() method into a getter.
        if self._email:
            return self._email
        else:
            return "No associated email"

    @email.setter
    def email(self, email_address) -> None:
        # A property object also has a setter method used as a decorator that
        # creates a copy of the property with the corresponding accessor
        # function set to the decorated function. Therefore, the setter
        # decorator for email is @email.setter. And a client can access it
        # by doing c.email = email@email.com if c is an instance of Contact.
        if re.fullmatch(r"^\S+@\S+$", email_address):
            self._email = email_address
        else:
            raise ValueError(f"{email_address} is invalid.")

(The sample code is available at Github.)

The @property decorator provides a nice way to implement getters and setters, and we can apply some operations or checks on them. The @property decorator also increases the readability of the code of getters and setters for the code developer and the client. In the example above, a client code can access the attributes like the following:

Python
contact = Contact(first_name="John", last_name="Wick")
contact.email = "john.wick@email.com"
print(f"Name: {contact.name}")
print(f"Email: {contact.email}")

The output would look like the following:

Bash
Name: John Wick
Email: john.wick@email.com

Conclusion

Although @property does not prevent a client from accessing the underline members (_first_name, _last_name and _email in this article’s example), it offers a Pythonic way to define access functions (i.e., getters and setters). Any experienced Python programmer would know that they should access the @property members, not the internal members.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)