Back to Blog
Python

Python Getter Property: Using @property Effectively

python getter property: Learn how to implement getter properties in Python with @property, including computed values, validation, and read-only attributes.

pythonpropertiesdecoratorsobject-oriented-programminggetters-setters
Python getter property concept illustrated with a property decorator and a value being accessed.

python getter property requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.

In Python, a getter property is created by decorating a method with @property. This turns the method into an attribute that can be accessed without parentheses. The simplest form looks like this:

class Temperature: def __init__(self, celsius): self._celsius = celsius @property def celsius(self): return self._celsius

Now temp.celsius returns the stored value, and there is no way to accidentally call it as a method. The leading underscore on _celsius signals that the attribute is intended for internal use, though Python does not enforce this convention.

Creating a Getter Property with @property

The @property decorator is part of the built-in property class. When you apply it to a method, the method becomes a getter for an attribute of the same name. The method should take only self and return the value you want to expose.

class User: def __init__(self, first_name, last_name): self._first_name = first_name self._last_name = last_name @property def full_name(self): return f"{self._first_name} {self._last_name}"

Here full_name is not stored directly; it is computed from two private attributes. The caller uses user.full_name and receives a string. This is a common pattern for derived data.

Why Use a Property Instead of a Plain Attribute

A plain attribute like self.celsius = value is simpler and faster. Properties become valuable when you need to control access or compute values on the fly. For example, you can change the internal representation without breaking external code.

Consider a class that stores temperature in Celsius but needs to expose Fahrenheit:

class Temperature: def __init__(self, celsius): self._celsius = celsius @property def fahrenheit(self): return self._celsius * 9 / 5 + 32

If you later decide to store Fahrenheit internally, you can update the getter and the public interface remains unchanged. This decoupling is a primary reason to use properties.

Computed Properties That Derive Values

Computed properties are methods that calculate a result each time they are accessed. They are useful when the value depends on multiple attributes or external state. For example, a rectangle class:

class Rectangle: def __init__(self, width, height): self.width = width self.height = height @property def area(self): return self.width * self.height

Every time you access rect.area, it multiplies the current width and height. If those attributes change, the area reflects the new values automatically. This avoids storing redundant data that could become stale.

Read-Only Properties and Encapsulation

A property without a setter is read-only. Attempting to assign to it raises an AttributeError. This is useful for values that should not be modified from outside the class.

class BankAccount: def __init__(self, account_number, balance): self._account_number = account_number self._balance = balance @property def account_number(self): return self._account_number @property def balance(self): return self._balance

Here both attributes are exposed as read-only. The only way to change balance is through methods like deposit or withdraw, which can enforce business rules. This encapsulation prevents external code from putting the object into an invalid state.

Validation and Side Effects in Getters

Getters can also perform validation or trigger side effects, though this is less common. A getter might check that a resource is still available or log access for auditing. However, keep in mind that getters are called whenever the attribute is read, so any side effect will occur on every access.

class CachedValue: def __init__(self, source): self._source = source self._cache = None @property def value(self): if self._cache is None: self._cache = self._source.fetch() return self._cache

This lazy-loading pattern defers expensive work until the value is actually needed. The getter checks the cache and only fetches from the source once. Subsequent accesses are cheap.

Performance and Maintainability Considerations

Properties add a method call overhead compared to direct attribute access. For most applications this is negligible, but in tight loops that access the same property millions of times, it can matter. If you measure a bottleneck, consider caching the value in a local variable before the loop.

Maintainability improves because the getter centralizes logic. If you need to change how a value is computed, you edit one method rather than every place that uses the attribute. However, avoid putting heavy computation in a getter without documentation, because callers expect attribute access to be fast.

Another consideration is that properties are class-level descriptors. They work on instances, but they are defined on the class. This means you cannot easily replace a property with a plain attribute on a per-instance basis without using __dict__ tricks, which is rarely advisable.

Common Mistakes and How to Avoid Them

One frequent mistake is forgetting that the getter method name becomes the attribute name. If you define a getter @property def name(self), you cannot also have an attribute self.name without causing a recursion error. Always store the backing value in a differently named attribute, typically with a leading underscore.

Another error is using a property for something that should be a method. If the operation takes arguments or has side effects beyond returning a value, a regular method is clearer. For example, list.pop() is a method, not a property, because it modifies the list.

Finally, do not use properties to hide expensive operations without caching. If a getter performs a database query or a network call, callers will not expect that cost from a simple attribute read. Document the behavior clearly or use a method like get_data() to signal the cost.

When you need a getter that also allows controlled updates, combine @property with a setter using the same method name. This gives you the familiar attribute syntax while keeping validation in one place. The setter is defined with @name.setter and is invoked on assignment. This pattern is the standard way to implement a getter property in Python that supports both reading and writing.

python getter property: Practical Usage and Code Examples | RYUSLOG DEV