Implementing Properties Using @property in Python
Course: Python Training
Topic: Implementing Properties Using @property
Course Link: Python Training Course
Demo Link: Register for a Free Course Demo
Website: JustAcademy
1. Introduction to Properties in Python
In Python, properties allow developers to control how attributes of a class are accessed, modified, and managed. The @property decorator enables a method to be accessed like an ordinary attribute without requiring parentheses.
Properties are particularly useful when implementing encapsulation, validating attribute values, calculating values dynamically, or maintaining compatibility when the internal implementation of a class changes.
For example, instead of directly accessing an internal attribute such as _price, a class can expose a property named price that controls how the value is retrieved or updated.
2. Learning Objectives
- Understand the purpose of the
@property decorator.
- Learn how getter, setter, and deleter methods work.
- Implement validation when modifying object attributes.
- Understand read-only and computed properties.
- Apply properties to real-world Python classes.
- Identify common mistakes and follow property implementation best practices.
3. What Is the @property Decorator?
The @property decorator is a built-in Python feature that converts a method into a managed attribute. This means the method can be called through attribute access rather than a function call.
Normally, a method is called using parentheses. However, when a method is decorated with @property, its value can be accessed without parentheses.
Example: A Basic Property
class Student:
def __init__(self, name):
self._name = name
@property
def name(self):
return self._name
student = Student("Aarav")
print(student.name)
Output:
Aarav
Explanation:
Student is a class that represents a student.
_name is an internal attribute used to store the student's name.
@property makes the name() method accessible as student.name.
- The method returns the stored value without exposing the internal attribute directly through the public interface.
4. Understanding Encapsulation and Properties
Encapsulation is an object-oriented programming principle that combines data and behavior within a class and controls how the object's internal state is accessed or modified.
Python does not enforce traditional private attributes in the same way as some other programming languages. Instead, naming conventions and mechanisms such as properties help establish controlled access.
Example Without a Property
class Product:
def __init__(self, price):
self.price = price
product = Product(500)
product.price = -100
print(product.price)
Output:
-100
This implementation allows an invalid price because no validation is performed.
Example With a Property
class Product:
def __init__(self, price):
self.price = price
@property
def price(self):
return self._price
@price.setter
def price(self, value):
if value < 0:
raise ValueError("Price cannot be negative")
self._price = value
product = Product(500)
print(product.price)
product.price = 700
print(product.price)
Output:
500
700
If a negative value is assigned, Python raises a ValueError. The property ensures that the object's price follows the required validation rule.
5. Getter Methods Using @property
A getter retrieves the value of an attribute. In Python, the @property decorator defines the getter method.
Syntax
class ClassName:
@property
def attribute_name(self):
return self._attribute_name
Example
class Employee:
def __init__(self, employee_id):
self._employee_id = employee_id
@property
def employee_id(self):
return self._employee_id
employee = Employee(101)
print(employee.employee_id)
Output:
101
The getter provides a controlled way to retrieve the employee ID. The caller uses employee.employee_id instead of calling a method with parentheses.
6. Setter Methods Using @property
A setter controls how an attribute is updated. It is defined using the @attribute_name.setter decorator.
Syntax
@attribute_name.setter
def attribute_name(self, value):
# Validate or process the value
self._attribute_name = value
Example: Validating Age
class Person:
def __init__(self, age):
self.age = age
@property
def age(self):
return self._age
@age.setter
def age(self, value):
if not isinstance(value, int) or isinstance(value, bool):
raise TypeError("Age must be an integer")
if value < 0:
raise ValueError("Age cannot be negative")
self._age = value
person = Person(25)
print(person.age)
person.age = 30
print(person.age)
Output:
25
30
The setter checks the incoming value before storing it. If the value is not an integer or is negative, an appropriate exception is raised.
7. Deleter Methods Using @property
A deleter defines what happens when a property is deleted using the del statement. It is declared using the @attribute_name.deleter decorator.
Example
class Account:
def __init__(self, account_name):
self._account_name = account_name
@property
def account_name(self):
return self._account_name
@account_name.deleter
def account_name(self):
del self._account_name
account = Account("Savings")
print(account.account_name)
del account.account_name
Output before deletion:
Savings
After deletion, accessing account.account_name raises an AttributeError because the underlying attribute no longer exists.
Use deleters only when deletion is part of the intended class interface. For important business objects, an explicit method may be clearer than allowing property deletion.
8. Creating Read-Only Properties
A read-only property provides access to a value without allowing the caller to assign a new value through that property. A getter can be defined without a corresponding setter.
Example
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def radius(self):
return self._radius
circle = Circle(5)
print(circle.radius)
Output:
5
Attempting to assign circle.radius = 10 raises an AttributeError because the property has no setter.
Important: A read-only property prevents assignment through that public property, but it does not make the underlying attribute completely private or immutable. Python code can still modify circle._radius by convention.
9. Creating Computed Properties
A computed property returns a value calculated from other attributes. It is useful when a value should always reflect the current state of an object without being stored separately.
Example: Rectangle Area
class Rectangle:
def __init__(self, length, width):
self.length = length
self.width = width
@property
def area(self):
return self.length * self.width
rectangle = Rectangle(10, 5)
print(rectangle.area)
rectangle.width = 8
print(rectangle.area)
Output:
50
80
The area is recalculated whenever the property is accessed. Updating the width automatically changes the returned area.
10. Using @property for Data Validation
Properties help maintain valid object states by checking incoming values before they are stored. This approach is useful for ages, prices, usernames, temperatures, percentages, and other attributes with specific rules.
Example: Bank Account Balance
class BankAccount:
def __init__(self, balance):
self.balance = balance
@property
def balance(self):
return self._balance
@balance.setter
def balance(self, value):
if value < 0:
raise ValueError("Balance cannot be negative")
self._balance = value
account = BankAccount(1000)
print(account.balance)
account.balance = 1500
print(account.balance)
Output:
1000
1500
This example prevents the balance from being set to a negative value. A production banking system would also need appropriate transaction rules, authorization, concurrency handling, and other safeguards.
11. Naming Conventions for Properties
A common Python convention is to use a leading underscore for an internal attribute and expose a public property with a descriptive name.
| Attribute |
Purpose |
Example |
_name |
Internal storage attribute |
self._name = name |
name |
Public property |
person.name |
@property |
Defines the getter |
def name(self) |
@name.setter |
Defines the setter |
def name(self, value) |
@name.deleter |
Defines deletion behavior |
def name(self) |
The leading underscore communicates that an attribute is intended for internal use. It is a convention rather than a strict access restriction.
12. Complete Example: Student Grade Management
This example combines getters, setters, validation, and a computed property in a single class.
class Student:
def __init__(self, name, marks):
self.name = name
self.marks = marks
@property
def name(self):
return self._name
@name.setter
def name(self, value):
if not isinstance(value, str) or not value.strip():
raise ValueError("Name cannot be empty")
self._name = value.strip()
@property
def marks(self):
return self._marks
@marks.setter
def marks(self, value):
if not isinstance(value, (int, float)) or isinstance(value, bool):
raise TypeError("Marks must be numeric")
if not 0 <= value <= 100:
raise ValueError("Marks must be between 0 and 100")
self._marks = value
@property
def result(self):
return "Pass" if self.marks >= 40 else "Fail"
student = Student("Riya", 85)
print(student.name)
print(student.marks)
print(student.result)
student.marks = 35
print(student.result)
Output:
Riya
85
Pass
Fail
Explanation:
- The
name property validates and stores a non-empty string.
- The
marks property accepts numeric values between 0 and 100.
- The
result property calculates the student's result from the current marks.
- When marks change from 85 to 35, the computed result changes from Pass to Fail.
13. How @property Works Internally
The @property decorator creates a property descriptor. When code accesses the corresponding attribute, Python uses the descriptor protocol to determine how to retrieve, assign, or delete the value.
Consider the following code:
class Example:
@property
def value(self):
return 100
obj = Example()
print(obj.value)
Python invokes the property's getter when obj.value is accessed. The caller does not need to know that a method is responsible for returning the value.
Properties also support setter and deleter methods, which define how assignment and deletion are handled. This allows a class to expose a simple attribute-style interface while controlling its implementation.
14. Difference Between Normal Attributes and Properties
| Feature |
Normal Attribute |
Property |
| Access |
Directly reads stored data |
Can execute getter logic |
| Assignment |
Usually stores the assigned value |
Can validate or transform the value |
| Computed values |
Must be calculated separately or stored |
Can calculate values dynamically |
| Read-only behavior |
Requires additional design |
Possible without a setter |
| Interface |
Simple attribute access |
Same attribute-style access with controlled behavior |
| Typical use |
Simple data storage |
Validation, computed values, and controlled access |
15. Difference Between @property and Getter/Setter Methods
Some programming languages commonly use explicit methods such as get_name() and set_name(). Python supports these methods, but properties often provide a more natural interface.
# Explicit getter and setter methods
class Person:
def __init__(self, name):
self._name = name
def get_name(self):
return self._name
def set_name(self, value):
self._name = value
person = Person("Aarav")
print(person.get_name())
person.set_name("Riya")
# Property-based access
class PersonWithProperty:
def __init__(self, name):
self._name = name
@property
def name(self):
return self._name
@name.setter
def name(self, value):
self._name = value
person2 = PersonWithProperty("Aarav")
print(person2.name)
person2.name = "Riya"
Properties preserve a straightforward attribute-style interface. Explicit methods remain useful when an operation represents an action, performs a complex process, or needs a descriptive command name.
16. Common Mistakes When Using @property
Mistake 1: Using the Public Property Name for Internal Storage
class Product:
@property
def price(self):
return self.price
This creates recursion because accessing self.price calls the same getter repeatedly.
Correct approach:
class Product:
def __init__(self, price):
self._price = price
@property
def price(self):
return self._price
Mistake 2: Forgetting the Setter Decorator
class Person:
@property
def age(self):
return self._age
def age(self, value):
self._age = value
The second method replaces the first method in the class namespace. It does not define a property setter.
Correct approach:
@age.setter
def age(self, value):
self._age = value
Mistake 3: Not Validating Values
A setter that stores every incoming value without checking it may allow invalid data into the object. Add validation when the attribute has clear requirements.
Mistake 4: Adding Properties Without a Real Need
Not every attribute requires a property. For simple values without additional behavior, ordinary attributes are often easier to read and maintain.
Mistake 5: Assuming a Property Makes Data Completely Private
A property controls access through its public interface, but a leading underscore does not prevent other code from accessing the internal attribute directly. Properties are not a complete security mechanism.
17. Best Practices for Implementing Properties
- Use descriptive property names that represent the data being exposed.
- Use leading underscores for internal storage attributes when appropriate.
- Keep getters simple and predictable.
- Validate incoming values in setters when necessary.
- Raise appropriate exceptions such as
ValueError and TypeError.
- Use read-only properties for values that should not be assigned through the public interface.
- Use computed properties when the result depends on other current attributes.
- Avoid expensive operations or unexpected side effects inside getters.
- Do not create a setter or deleter unless the class requires that behavior.
- Write tests for valid values, invalid values, and boundary conditions.
18. Practical Applications of @property
| Application |
Property Use |
Example |
| E-commerce |
Validate product prices |
product.price |
| Education |
Validate marks and calculate results |
student.result |
| Banking |
Control access to account-related values |
account.balance |
| Geometry |
Calculate area dynamically |
rectangle.area |
| Employee management |
Validate employee details |
employee.salary |
| Configuration systems |
Normalize or validate settings |
settings.timeout |
19. Practice Exercises
Exercise 1: Employee Salary
Create an Employee class with a salary property. Ensure that salary cannot be negative and allow valid salary updates.
Exercise 2: Temperature Converter
Create a Temperature class that stores a temperature in Celsius and exposes a computed Fahrenheit property using the formula:
fahrenheit = (celsius * 9 / 5) + 32
Exercise 3: Rectangle Area
Create a Rectangle class with length and width attributes. Implement an area property that returns the product of the two values.
Exercise 4: Username Validation
Create a User class with a username property. Reject empty usernames and remove unnecessary whitespace before storing the value.
Exercise 5: Read-Only Identification
Create a class that generates an ID during initialization and exposes it through a read-only property.
20. Mini Project: Product Inventory Management
Objective: Build a small inventory system using properties to validate product details and calculate stock value.
Requirements
- Create a
Product class with a name, price, and quantity.
- Use properties to retrieve and update price and quantity.
- Reject negative prices and quantities.
- Create a computed
stock_value property.
- Display product details and the total inventory value.
Sample Implementation
class Product:
def __init__(self, name, price, quantity):
self.name = name
self.price = price
self.quantity = quantity
@property
def price(self):
return self._price
@price.setter
def price(self, value):
if not isinstance(value, (int, float)) or isinstance(value, bool):
raise TypeError("Price must be numeric")
if value < 0:
raise ValueError("Price cannot be negative")
self._price = value
@property
def quantity(self):
return self._quantity
@quantity.setter
def quantity(self, value):
if not isinstance(value, int) or isinstance(value, bool):
raise TypeError("Quantity must be an integer")
if value < 0:
raise ValueError("Quantity cannot be negative")
self._quantity = value
@property
def stock_value(self):
return self.price * self.quantity
product = Product("Keyboard", 1200, 10)
print("Product:", product.name)
print("Price:", product.price)
print("Quantity:", product.quantity)
print("Stock Value:", product.stock_value)
Output:
Product: Keyboard
Price: 1200
Quantity: 10
Stock Value: 12000
Project takeaway: The property setters enforce basic data rules, while the computed property calculates the inventory value using the current price and quantity.
21. Interview Questions and Answers
Q1. What is the @property decorator in Python?
Answer: It converts a method into a property, allowing the method to be accessed like an attribute.
Q2. What is a getter?
Answer: A getter retrieves an attribute's value. It is commonly defined using @property.
Q3. What is a setter?
Answer: A setter controls assignment to a property and can validate or transform the incoming value.
Q4. How do you define a property setter?
Answer: Use the property's name followed by .setter, as in @age.setter.
Q5. Can a property be read-only?
Answer: Yes. Define a getter without a setter. Assignment through that property will fail.
Q6. What is a computed property?
Answer: It calculates and returns a value when accessed instead of storing that result separately.
Q7. Why use an underscore in an attribute name?
Answer: A leading underscore indicates that an attribute is intended for internal use. It is a naming convention, not strict access control.
Q8. What happens when a property setter receives an invalid value?
Answer: The setter can raise an exception, such as ValueError or TypeError, to reject the value.
Q9. What is the purpose of @property.deleter functionality?
Answer: The @attribute.deleter decorator defines behavior when the property is deleted with del.
Q10. When should properties be avoided?
Answer: Avoid them when ordinary attribute access is sufficient or when the getter would perform complex, expensive, or surprising operations.
22. Quick Revision Notes
@property turns a method into an attribute-style getter.
@name.setter defines how a property is assigned.
@name.deleter defines how a property is deleted.
- Properties can support validation and controlled updates.
- Read-only properties have no setter.
- Computed properties calculate values dynamically.
- Internal storage commonly uses a leading underscore, such as
_name.
- Properties help maintain a clean public interface while allowing implementation details to change.
- Use properties when they provide real value, not automatically for every attribute.
23. Final Checklist
- Understand the difference between attributes and properties.
- Implement getters using
@property.
- Implement setters with validation.
- Use read-only properties where appropriate.
- Create computed properties based on other attributes.
- Understand how deleters work.
- Practice implementing properties in employee, student, and inventory classes.
- Test valid values, invalid values, and boundary conditions.
Conclusion
The @property decorator is an important Python feature for implementing encapsulation, validation, read-only access, and computed attributes. It lets developers provide a simple attribute-style interface while retaining control over how values are retrieved, updated, or deleted.
By practicing getters, setters, deleters, and computed properties, you can build Python classes that are easier to maintain, test, and extend. These concepts are especially useful in object-oriented programming and real-world application development.
Learn More with JustAcademy
Explore the Python Training Course to strengthen your Python programming and object-oriented development skills.
Interested in learning more? Register for a Course Demo to explore the training options.
Visit JustAcademy for more learning resources and courses.