Python24 min read

Python Properties

Use @property to create clean getter/setter logic with validation, computed attributes, and read-only fields, without breaking your public API.

David Miller
August 25, 2025
3.8k137

Properties let you access methods like attributes.

      Instead of:
      - person.get_name()
      - person.set_name("Sarah")
      
      You can write:
      - person.name
      - person.name = "Sarah"
      
      But still run validation behind the scenes.
      
      ## Why properties matter
      
      They help you:
      - validate inputs
      - create read-only attributes
      - compute values dynamically
      - keep your API clean
      
      ## Basic property (getter + setter)
      
      ```python
      class Person:
          def __init__(self, name):
              self._name = name  # underscore = internal
      
          @property
          def name(self):
              return self._name
      
          @name.setter
          def name(self, value):
              if not value or not value.strip():
                  raise ValueError("Name cannot be empty")
              self._name = value.strip()
      
      p = Person("Tom")
      print(p.name)
      
      p.name = " Sarah "
      print(p.name)
      ```
      
      Expected output:
      ```
      Tom
      Sarah
      ```
      
      ## Read-only property
      
      ```python
      import math
      
      class Circle:
          def __init__(self, radius):
              self.radius = radius
      
          @property
          def area(self):
              return math.pi * (self.radius ** 2)
      
      c = Circle(5)
      print(round(c.area, 2))
      ```
      
      Here, `area` has no setter, so it cannot be directly changed.
      
      ## Computed property with two-way conversion
      
      ```python
      class Temperature:
          def __init__(self, celsius):
              self._celsius = celsius
      
          @property
          def celsius(self):
              return self._celsius
      
          @property
          def fahrenheit(self):
              return (self._celsius * 9/5) + 32
      
          @fahrenheit.setter
          def fahrenheit(self, value):
              self._celsius = (value - 32) * 5/9
      
      t = Temperature(25)
      print(t.fahrenheit)
      
      t.fahrenheit = 86
      print(round(t.celsius, 2))
      ```
      
      Expected output:
      ```
      77.0
      30.0
      ```
      
      ## Real-world example: bank account validation
      
      ```python
      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
      
      acc = BankAccount(1000)
      acc.balance = 1200
      print(acc.balance)
      
      # acc.balance = -10  # would raise error
      ```
      
      ## Graph: property access
      
      ```mermaid
      flowchart LR
        A[Read: obj.name] --> B[@property getter]
        C[Write: obj.name = value] --> D[@name.setter]
        B --> E[Return data]
        D --> F[Validate + store]
      ```
      
      ## Key points
      
      - Properties look like attributes, behave like methods
      - Great for validation and computed values
      - Keeps public API stable even if internals change
      
#Python#Advanced#OOP