6.5. Property

6.5.1. Rationale

  • Disable attribute modification

  • Logging value access

  • Check boundary

  • Raise exceptions (TypeError)

  • Check argument type

6.5.2. Protocol

  • value = property() - creates property

  • @value.getter - getter for attribute (value has to be property)

  • @value.setter - setter for attribute (value has to be property)

  • @value.deleter - deleter for attribute (value has to be property)

  • @property - creates property and a getter

Syntax:
class MyClass:

    @property
    def myattribute(self):
        return ...
Alternative:
class MyClass:
    myattribute = property()

    @myattribute.getter
    def myattribute(self):
        return ...
Are equivalent to:
class MyClass:

    def myattribute(self):
        return ...

    myattribute = property(myattribute)

6.5.3. Example

class Astronaut:
    name = property()

    def __init__(self, firstname, lastname):
        self._firstname = firstname
        self._lastname = lastname

    @property
    def name(self):
        return f'{self._firstname} {self._lastname[0]}.'


astro = Astronaut('Mark', 'Watney')
print(astro.name)
# Mark W.
class Temperature:
    kelvin = property()

    def __init__(self, kelvin=None):
        self._kelvin = kelvin

    @kelvin.setter
    def kelvin(self, value):
        if value < 0:
            raise ValueError('Negative Kelvin Temperature')


t = Temperature()
t.kelvin = 10
t.kelvin = -1
# Traceback (most recent call last):
#     ...
# ValueError: Negative Kelvin Temperature

6.5.4. Attribute Access

  • Java way: Setter and Getter

  • Pythonic way: Properties, Reflection, Descriptors

Listing 6.94. Accessing class fields using setter and getter
class Astronaut:
    def __init__(self, name=None):
        self._name = name

    def set_name(self, name):
        self._name = name

    def get_name(self):
        return self._name


astro = Astronaut()
astro.set_name('Mark Watney')
print(astro.get_name())
# Mark Watney
Listing 6.95. Accessing class fields. Either put name as an argument for __init__() or create dynamic field in runtime
class Astronaut:
    def __init__(self, name=None):
        self.name = name


astro = Astronaut()
astro.name = 'Jan Twardowski'
print(astro.name)
# Jan Twardowski

6.5.5. Property class

  • Property's arguments are method pointers get_name, set_name, del_name and a docstring

  • Don't do that

class Astronaut:
    def __init__(self, name=None):
        self._name = name

    def get_name(self):
        return self._name

    def set_name(self, value):
        self._name = value

    def del_name(self):
        del self._name

    name = property(get_name, set_name, del_name, "I am the 'name' property.")

6.5.6. @property Decorator

  • Prefer name = property()

class Astronaut:
    name = property()

    def __init__(self, name=None):
        self._name = name

    @name.getter
    def name(self):
        return self._name

    @name.setter
    def name(self, value):
        self._name = value

    @name.deleter
    def name(self):
        del self._name
class Astronaut:
    def __init__(self, name=None):
        self._name = name

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, value):
        self._name = value

    @name.deleter
    def name(self):
        del self._name

6.5.7. Use Cases

6.5.7.1. Astronaut

class Astronaut:
    def __init__(self):
        self._name = None

    def set_name(self, name):
        self._name = name.title()

    def get_name(self):
        if self._name:
            firstname, lastname = self._name.split()
            return f'{firstname} {lastname[0]}.'

    def del_name(self):
        self._name = None


astro = Astronaut()

astro.set_name('JaN TwARdoWskI')
print(astro.get_name())
# Jan T.

astro.del_name()
print(astro.get_name())
# None
class Astronaut:
    name = property()

    def __init__(self):
        self._name = None

    @name.getter
    def name(self):
        if self._name:
            firstname, lastname = self._name.split()
            return f'{firstname} {lastname[0]}.'

    @name.setter
    def name(self, name):
        self._name = name.title()

    @name.deleter
    def name(self):
        self._name = None


astro = Astronaut()

astro.name = 'JAN TwARdoWski'
print(astro.name)
# Jan T.

del astro.name
print(astro.name)
# None

6.5.7.2. Temperature

class Temperature:
    def __init__(self, initial_temperature):
        self._protected = initial_temperature

    @property
    def value(self):
        print('You are trying to access a value')
        return self._protected


t = Temperature(100)

print(t.value)
# You are trying to access a value
# 100
class Temperature:
    def __init__(self, initial_temperature):
        self._protected = initial_temperature

    @property
    def value(self):
        return self._protected

    @value.setter
    def value(self, new_value):
        if new_value < 0.0:
            raise ValueError('Kelvin Temperature cannot be negative')
        else:
            self._protected = new_value


t = Temperature(100)
t.value = -10
# Traceback (most recent call last):
#     ...
# ValueError: Kelvin Temperature cannot be negative
class Temperature:
    def __init__(self, initial_temperature):
        self._protected = initial_temperature

    @property
    def value(self):
        return self._protected

    @value.deleter
    def value(self):
        print('Resetting temperature')
        self._protected = 0.0


t = Temperature(100)

del t.value
# Resetting temperature

print(t.value)
# 0.0

6.5.8. Assignments

6.5.8.1. Protocol Property Getter

  • Assignment name: Protocol Property Getter

  • Last update: 2020-10-16

  • Complexity level: easy

  • Lines of code to write: 9 lines

  • Estimated time of completion: 5 min

  • Solution: solution/protocol_property_getter.py

English
  1. Define class Point with x, y, z attributes

  2. Define property position in class Point

  3. Accessing position returns (x, y, z)

  4. Compare result with "Output" section (see below)

Polish
  1. Zdefiniuj klasę Point z atrybutami x, y, z

  2. Zdefiniuj property position w klasie Point

  3. Dostęp do position zwraca (x, y, z)

  4. Porównaj wyniki z sekcją "Output" (patrz poniżej)

Output
>>> pt = Point(x=1, y=2, z=3)
>>> pt.x, pt.y, pt.z
(1, 2, 3)
>>> pt.position
(1, 2, 3)

6.5.8.2. Protocol Property Setter

  • Assignment name: Protocol Property Setter

  • Last update: 2020-10-16

  • Complexity level: easy

  • Lines of code to write: 9 lines

  • Estimated time of completion: 5 min

  • Solution: solution/protocol_property_setter.py

English
  1. Define class Point with x, y, z attributes

  2. Define property position in class Point

  3. Deleting position sets all attributes to 0 (x=0, y=0, z=0)

  4. Compare result with "Output" section (see below)

Polish
  1. Zdefiniuj klasę Point z atrybutami x, y, z

  2. Zdefiniuj property position w klasie Point

  3. Usunięcie position ustawia wszystkie atrybuty na 0 (x=0, y=0, z=0)

  4. Porównaj wyniki z sekcją "Output" (patrz poniżej)

Output
>>> pt = Point(x=1, y=2, z=3)
>>> pt.x, pt.y, pt.z
(1, 2, 3)
>>> pt.position = (4, 5, 6)
Traceback (most recent call last):
    ...
PermissionError: Cannot modify values

6.5.8.3. Protocol Property Deleter

  • Assignment name: Protocol Property Deleter

  • Last update: 2020-10-16

  • Complexity level: easy

  • Lines of code to write: 11 lines

  • Estimated time of completion: 5 min

  • Solution: solution/protocol_property_deleter.py

English
  1. Define class Point with x, y, z attributes

  2. Define property position in class Point

  3. Deleting position sets all attributes to 0 (x=0, y=0, z=0)

  4. Compare result with "Output" section (see below)

Polish
  1. Zdefiniuj klasę Point z atrybutami x, y, z

  2. Zdefiniuj property position w klasie Point

  3. Usunięcie position ustawia wszystkie atrybuty na 0 (x=0, y=0, z=0)

  4. Porównaj wyniki z sekcją "Output" (patrz poniżej)

Output
>>> pt = Point(x=1, y=2, z=3)
>>> pt.x, pt.y, pt.z
(1, 2, 3)
>>> del pt.position
>>> pt.x. pt,y, pt.z
(0, 0, 0)