All Courses
Object-Oriented Programming

Python Properties

What is a Property?

A property is a special attribute that lets you control what happens when an attribute is accessed or modified. It combines a getter and setter behind a clean, attribute-style interface — no need to call methods explicitly.


The Problem Properties Solve

By default, any attribute can be changed freely from outside a class:

class Person:
    def __init__(self, name):
        self.name = name
        self.salary = 0

p = Person("Tim")
p.salary = -100   # Nothing stops this — but negative salary makes no sense!

Private Attributes

To signal that an attribute shouldn't be touched from outside the class, prefix it with an underscore:

self._salary = 0
  • This is a convention, not a true restriction — Python doesn't enforce it
  • The underscore means: "treat this as private; don't access it directly outside the class"
  • The opposite, a public attribute, has no underscore and is freely accessible

Naming Classes (Bonus Tip from the Lesson)

  • Use PascalCase: Person, BankAccount
  • Name should be singular — every instance is one of whatever the class is named
  • Persons — instances aren't multiple people, they're one person
  • Person — just like Python's int, not ints

Setting Up a Property — Two Ways

Method 1: Legacy (property() function)

class Person:
    def __init__(self, name):
        self.name = name
        self._salary = 0

    def get_salary(self):
        return round(self._salary)

    def set_salary(self, salary):
        if salary < 0:
            raise ValueError("Hey, this is invalid!")
        self._salary = salary

    salary = property(get_salary, set_salary)
  • Pass the getter first, then the setter
  • The property name (salary) is what you use to access the attribute

Method 2: Decorator (Preferred ✅)

class Person:
    def __init__(self, name):
        self.name = name
        self._salary = 0

    @property
    def salary(self):                    # getter — named after the property
        return round(self._salary)

    @salary.setter
    def salary(self, salary):            # setter — same name, decorated with @<name>.setter
        if salary < 0:
            raise ValueError("Hey, this is invalid!")
        self._salary = salary

Rules for the decorator approach: - @property decorates the getter - @<property_name>.setter decorates the setter - Both methods must have the same name (the property name) - The getter must be defined above the setter - The underlying stored value must use a private attribute (e.g. _salary) — using the same name causes infinite recursion


Full Example — Time Class

class Time:
    def __init__(self, second):
        self._second = second   # uses the setter implicitly? No — direct assignment here

    @property
    def second(self):
        return self._second

    @second.setter
    def second(self, second):
        if second < 0 or second > 60:
            raise ValueError("Invalid!")
        self._second = second

t = Time(54)
t.second = 100   # raises ValueError: Invalid!
t.second = 59    # works fine
print(t.second)  # 59

How Properties Work Under the Hood

Operation What actually runs
p.salary = 100 Calls the setter with 100
x = p.salary Calls the getter, returns the result

The user writes clean attribute-style code, but the getter/setter logic runs invisibly.


Key Takeaways & Recap

Concept Summary
Property A special attribute backed by getter/setter logic
Private attribute Prefixed with _; signals "don't touch from outside"
@property Decorates the getter method
@name.setter Decorates the setter method
Same name rule Getter and setter must share the exact same method name
Why use it? Enforce constraints (e.g. no negative salary, seconds 0–60) while keeping clean attribute syntax