#! /usr/bin/env python3 # # Pure Python runtime property to expression binding example using descriptors # (see below for usage example). # # This Property implementation allows setting a value using lambda expressions. # The value will be recalculated if any property accessed during lambda evaluation # changes its value. class Property: _capture_observer = None def __init__(self, default_value=None): self.default_value = default_value def __get__(self, instance, owner=None): if self._capture_observer is not None: if self.observers_name not in instance.__dict__: instance.__dict__[self.observers_name] = set() instance.__dict__[self.observers_name].add(self._capture_observer) value = instance.__dict__.get(self.value_name) return value or self.default_value def __set__(self, instance, value_or_func): if callable(value_or_func): instance.__dict__[self.value_func_name] = value_or_func self._capture_observer = (instance, self) instance.__dict__[self.value_name] = value_or_func() self._capture_observer = None else: instance.__dict__[self.value_func_name] = None instance.__dict__[self.value_name] = value_or_func if self.observers_name in instance.__dict__: observers = instance.__dict__[self.observers_name] if len(observers) > 0: notify_observers = observers.copy() observers.clear() for instance, p in notify_observers: p.update_value(instance) def __set_name__(self, owner, name): self.value_name = '__property_{}_value'.format(name) self.value_func_name = '__property_{}_func'.format(name) self.observers_name = '__property_{}_observers'.format(name) def update_value(self, instance): value_func = instance.__dict__[self.value_func_name] self._capture_observer = instance, self instance.__dict__[self.value_name] = value_func() self._capture_observer = None class Employee: salary = Property() def __init__(self, **kwargs): for k, v in kwargs.items(): setattr(self, k, v) bob = Employee(name='Bob', salary=50) tom = Employee(name='Tom', salary=lambda: bob.salary * 2) print("before:") print("bob's salary:", bob.salary) # 50 print("tom's salary:", tom.salary) # 100 bob.salary = 60 print("after:") print("bob's salary:", bob.salary) # 60 print("tom's salary:", tom.salary) # 120