5. Properties

5.1. Accessing fields

5.1.1. Setter and Getter methods

  • Java way

  • don't do that in Python

Listing 361. Accessing class fields using setter and getter
class Astronaut:
    name = ''

    def set_name(self, name):
        print('Log, that value is being changed')
        self.name = name

    def get_name(self):
        return self.name


astro = Astronaut()
astro.set_name('Jan Twardowski')

print(astro.get_name())

Then your code starts to look like this:

class MyClass:
    def __init__(self):
        self._x = None

    def get_x(self):
        return self._x

    def set_x(self, value):
        self._x = value

    def del_x(self):
        del self._x

5.1.2. Rationale for setter and getter methods

Listing 362. HabitatOS Z-Wave sensor admin
from django.contrib import admin
from habitat._common.admin import HabitatAdmin
from habitat.sensors.models import ZWaveSensor


@admin.register(ZWaveSensor)
class ZWaveSensorAdmin(HabitatAdmin):
    change_list_template = 'sensors/change_list_charts.html'
    list_display = ['mission_date', 'mission_time', 'type', 'device', 'value', 'unit']
    list_filter = ['created', 'type', 'unit', 'device']
    search_fields = ['^date', 'device']
    ordering = ['-datetime']

    def get_list_display(self, request):
        list_display = self.list_display

        if request.user.is_superuser:
            list_display = ['earth_datetime'] + list_display

        return list_display

5.1.3. Direct attribute access

  • the Python way

Listing 363. Accessing class fields
class Astronaut:
    def __init__(self, name=''):
        self.name = name


astro = Astronaut()              # either put ``name`` as an argument for ``__init__()``
astro.name = 'Jan Twardowski'     # or create dynamic field in runtime

print(astro.name)

5.2. Properties

5.2.1. Rationale

  • Disable attribute modification

  • Logging value access

  • Check boundary

  • Raise exceptions (TypeError)

  • Check argument type

5.2.2. Property class

property()
# <property object at 0x10ff07940>

property().getter
# <built-in method getter of property object at 0x10ff07998>

property().setter
# <built-in method setter of property object at 0x10ff07940>

property().deleter
# <built-in method deleter of property object at 0x10ff07998>

5.2.3. Property decorator

  • @decorator syntax is just syntactic sugar; the syntax:

    @property
    def x(self):
        return self._x
    
  • really means the same thing as

    def x(self):
        return self._x
    
    x = property(x)
    

5.2.4. Creating properties with property class

  • Property's arguments are method pointers get_x, set_x, del_x and a docstring

Listing 364. Properties
class MyClass:
    def __init__(self):
        self._protected = None

    def get_x(self):
        return self._protected

    def set_x(self, value):
        self._protected = value

    def del_x(self):
        del self._protected

    x = property(get_x, set_x, del_x, "I am the 'x' property.")

5.2.5. Creating properties with @property decorator

class MyClass:
    def __init__(self):
        self._protected = None

    @property
    def x(self):
        pass

    @x.getter
    def x(self):
        return self._protected

    @x.setter
    def x(self, value):
        self._protected = value

    @x.deleter
    def x(self):
        del self._protected
Listing 365. Properties as a decorators
class MyClass:
    def __init__(self):
        self._protected = None

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

    @x.setter
    def x(self, value):
        self._protected = value

    @x.deleter
    def x(self):
        del self._protected

5.3. Use Cases

5.3.1. Getter

  • @property - for defining getters

Listing 366. Using @property as a getter
class Temperature:
    def __init__(self, initial_temperature):
        self._protected = initial_temperature

    @property
    def value(self):
        pass

    @value.getter
    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
Listing 367. Using @property as a getter
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

5.3.2. Setter

  • @x.setter - defining setter for field x

  • Require field to be @property

Listing 368. @x.setter
class Temperature:
    def __init__(self, initial_temperature):
        self._protected = initial_temperature

    @property
    def value(self):
        pass

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

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


t = Temperature(100)

print(t.value)
# 100

t.value = -10
# ValueError: Kelvin Temperature cannot be below zero

5.3.3. Deleter

  • @x.deleter - for defining deleter for field x

  • Require field to be @property

Listing 369. @x.deleter
class Temperature:
    def __init__(self, initial_temperature):
        self._protected = initial_temperature

    @property
    def value(self):
        pass

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

    @value.deleter
    def value(self):
        self._protected = 0.0


t = Temperature(100)

del t.value

print(t.value)
# 0

5.4. Assignments

5.4.1. Immutable classes

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

  2. Add position property which returns tuple (x, y, z)

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

  4. Prevent setting position

Polish
  1. Stwórz klasę Point z atrybutami x, y, z

  2. Dodaj property position, który zwraca tuple (x, y, z)

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

  4. Zablokuj edycję atrybutów