5.6. Reflection

5.6.1. Rationale

  • Act, when someone is trying to access an attribute

5.6.2. Syntax

5.6.2.1. Built-in Functions

  • hasattr(obj, 'attribute_name') -> bool

  • setattr(obj, 'attribute_name', 'new value') -> None

  • getattr(obj, 'attribute_name', 'default value') -> Any

  • delattr(obj, 'attribute_name') -> None

5.6.2.2. Protocol

  • __setattr__(self, attribute_name, value)

  • __getattribute__(self, attribute_name, default)

  • __getattr__(self, attribute_name, default)

  • __delattr__(self, attribute_name)

5.6.3. __setattr__()

  • Called when trying to set attribute to a value

  • setattr(x, 'name', 'value') is equivalent to x.name = 'value'

  • Call Stack:

    • astro.name = 'Mark Watney'

    • => setattr(astro, 'name', 'Mark Watney')

    • => obj.__setattr__('name', 'Mark Watney')

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

    def __setattr__(self, attribute_name, new_value):
        if attribute_name == 'value' and new_value < 0.0:
            raise ValueError('Kelvin temperature cannot be negative')
        else:
            object.__setattr__(self, attribute_name, new_value)


t = Temperature(100)

t.value = 20
print(t.value)
# 20

t.value = -10
# ValueError: Kelvin temperature cannot be negative

5.6.4. __delattr__()

  • Called when trying to delete attribute

  • delattr(x, 'name') is equivalent to del x.name

  • Call stack:

    • del astro.name

    • => delattr(astro, 'name')

    • => astro.__delattr__(name)

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

    def __delattr__(self, attribute_name):
        if attribute_name == 'value':
            self.value = 0.0
        else:
            object.__delattr__(self, attribute_name)


t = Temperature(100)

del t.value
print(t.value)
# 0.0

5.6.5. __getattribute__()

  • Called for every time, when you want to access any attribute

  • getattr(x, 'name') is equivalent to x.name

  • Before even checking if this attribute exists

  • if __getattribute__() raises AttributeError it calls __getattr__()

  • Call stack:

    • astro.name

    • => getattr(astro, 'name')

    • => astro.__getattribute__('name')

    • if astro.__getattribute__(name) raise AttributeError

    • => astro.__getattr__('name')

Listing 547. Example __getattribute__()
class Temperature:
    def __init__(self, initial_temperature):
        self.value = initial_temperature

    def __getattribute__(self, attribute_name):
        if attribute_name == 'value':
            raise PermissionError('Field is private')
        else:
            return object.__getattribute__(self, attribute_name)


temp = Temperature(273)

temp.value = 20
print(temp.value)
# PermissionError: Field is private

5.6.6. __getattr__()

  • Called whenever you request an attribute that hasn't already been defined

  • if __getattribute__() raises AttributeError it calls __getattr__()

  • Implementing a fallback for missing attributes

5.6.7. hasattr()

  • Check if object has attribute

  • There is no __hasattr__() method

  • Calls __getattribute__() and checks if raises AttributeError

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


t = Temperature(100)

hasattr(t, 'kelvin')
# False

hasattr(t, 'initial_temperature')
# False

hasattr(t, 'value')
# True

5.6.8. Assignments

5.6.8.1. Immutable classes

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

  2. Prevent adding new attributes

  3. Prevent deleting attributes

  4. Prevent changing attributes

  5. Allow to set attributes only at the initialization

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

  2. Zablokuj możliwość dodawania nowych atrybutów

  3. Zablokuj możliwość usuwania atrybutów

  4. Zablokuj edycję atrybutów

  5. Pozwól na ustawianie atrybutów tylko przy inicjalizacji klasy