Python · object-oriented

Properties

Methods that look like attributes: compute values, validate assignments, and make data read-only.

The @property decorator


          # @property for a getter
          class Circle:
              def __init__(self, radius):
                  self._radius = radius
              @property
              def radius(self):
                  return self._radius
          Circle(5).radius
          # ans: 5 (accessed like an attribute)
        
c.radius
looks like reading an attribute...
radius(c)
...but Python runs the getter method, with no ()
c._radius → 5
the real data lives in _radius

Property setter


        # @radius.setter
        class Circle:
            def __init__(self, radius):
                self._radius = radius
            @property
            def radius(self):
                return self._radius
            @radius.setter
            def radius(self, value):
                if value < 0:
                    raise ValueError("Negative")
                self._radius = value
        c = Circle(5)
        c.radius = 10
      
c.radius =

Assigning to the property runs the setter, which can reject bad values.

Computed property


          # property with a calculation
          class Circle:
              def __init__(self, radius):
                  self._radius = radius
              @property
              def area(self):
                  return 3.14 * self._radius ** 2
          Circle(5).area
          # ans: 78.5
        
Circle(5).area
no area is stored anywhere...
3.14 * 5 ** 2
...it is worked out on every access
3.14 * 25 → 78.5
so it is always up to date with the radius

Read-only property


        # property without a setter
        class Person:
            def __init__(self, name):
                self._name = name
            @property
            def name(self):
                return self._name
        person = Person("Alice")
        # ans: person.name = "Bob" raises AttributeError
      

Property deleter


        # @name.deleter
        class Person:
            def __init__(self, name):
                self._name = name
            @property
            def name(self):
                return self._name
            @name.deleter
            def name(self):
                del self._name
        person = Person("Alice")
        del person.name
      

del person.name runs the deleter, which removes _name.

Exercises

Predict each result, then press → to check.

Exercise 1


        # property vs method?
        class MyClass:
            @property
            def value(self):
                return 5
        obj = MyClass()
        obj.value
        # ans: 5 (no parentheses)
      

Exercise 2


        # setter without a getter?
        class MyClass:
            @value.setter
            def value(self, x):
                pass
        # ans: NameError (@property must define value first)
      

Exercise 3


        # assigning to a property?
        class MyClass:
            @property
            def value(self):
                return 5
        obj = MyClass()
        obj.value = 10
        # ans: AttributeError (no setter)
      

Exercise 4


        # is a property cached?
        class MyClass:
            @property
            def value(self):
                print("Computing")
                return 5
        obj = MyClass()
        obj.value
        obj.value
        # ans: prints "Computing" twice
      

Exercise 5


        # property validation?
        class MyClass:
            @property
            def x(self):
                return self._x
            @x.setter
            def x(self, value):
                if value < 0:
                    raise ValueError()
                self._x = value
        # ans: obj.x = -1 raises ValueError; obj.x = 3 stores 3 in _x
      

Exercise 6


        # property in __init__?
        class MyClass:
            def __init__(self, x):
                self.x = x  # calls the setter
            @property
            def x(self):
                return self._x
            @x.setter
            def x(self, value):
                self._x = value
        # ans: MyClass(3) goes through the setter and stores 3 in _x
      

So validation in a setter also protects the constructor.

Exercise 7


        # delete without a deleter?
        class MyClass:
            @property
            def value(self):
                return 5
        obj = MyClass()
        del obj.value
        # ans: AttributeError
      

Exercise 8


        # property inheritance?
        class Base:
            @property
            def x(self):
                return 5
        class Derived(Base):
            pass
        Derived().x
        # ans: 5
      

Exercise 9


        # overriding a property?
        class Base:
            @property
            def x(self):
                return 5
        class Derived(Base):
            @property
            def x(self):
                return 10
        Derived().x
        # ans: 10
      

Exercise 10


        # property with arguments?
        class MyClass:
            @property
            def value(self, arg):
                return arg
        # ans: TypeError when obj.value is read (a getter takes only self)
      

Remember

Getter

@property: read with obj.name, no parentheses.

Setter

@name.setter: runs on obj.name = v; validate here.

Deleter

@name.deleter: runs on del obj.name.

Read-only by default

No setter means assignment raises AttributeError.