Well, another way could be this:
Attr is an abstraction for "meaning". We need this because Python does not have a “destination overload” (the simple clean get / set paradigm is used as the cleanest alternative). Attr also acts as an “Observed”.
AttrSet is an “observer” for Attr s, which tracks their changes in values, effectively acting as an Attr -to-whatever dictionary ( person in our case).
create_with_attrs is a factory that creates what looks like a named tuple, accessing the forwarding attribute provided by Attr s, so person.name = "Ivan" effectively gives person.name_attr.set("Ivan") and makes an observation AttrSet is person name appropriately reorders their insides.
Code (tested):
from collections import defaultdict class Attribute(object): def __init__(self, value): super(Attribute, self).__init__() self._value = value self._notified_set = set() def set(self, value): old = self._value self._value = value for n_ch in self._notified_set: n_ch(old_value=old, new_value=value) def get(self): return self._value def add_notify_changed(self, notify_changed): self._notified_set.add(notify_changed) def remove_notify_changed(self, notify_changed): self._notified_set.remove(notify_changed) class AttrSet(object): def __init__(self): super(AttrSet, self).__init__() self._attr_value_to_obj_set = defaultdict(set) self._obj_to_attr = {} self._attr_to_notify_changed = {} def add(self, attr, obj): self._obj_to_attr[obj] = attr self._add(attr.get(), obj) notify_changed = (lambda old_value, new_value: self._notify_changed(obj, old_value, new_value)) attr.add_notify_changed(notify_changed) self._attr_to_notify_changed[attr] = notify_changed def get(self, *attr_value_lst): attr_value_lst = attr_value_lst or self._attr_value_to_obj_set.keys() result = set() for attr_value in attr_value_lst: result.update(self._attr_value_to_obj_set[attr_value]) return result def remove(self, obj): attr = self._obj_to_attr.pop(obj) self._remove(attr.get(), obj) notify_changed = self._attr_to_notify_changed.pop(attr) attr.remove_notify_changed(notify_changed) def __iter__(self): return iter(self.get()) def _add(self, attr_value, obj): self._attr_value_to_obj_set[attr_value].add(obj) def _remove(self, attr_value, obj): obj_set = self._attr_value_to_obj_set[attr_value] obj_set.remove(obj) if not obj_set: self._attr_value_to_obj_set.pop(attr_value) def _notify_changed(self, obj, old_value, new_value): self._remove(old_value, obj) self._add(new_value, obj) def create_with_attrs(**attr_name_to_attr): class Result(object): def __getattr__(self, attr_name): if attr_name in attr_name_to_attr.keys(): return attr_name_to_attr[attr_name].get() else: raise AttributeError(attr_name) def __setattr__(self, attr_name, attr_value): if attr_name in attr_name_to_attr.keys(): attr_name_to_attr[attr_name].set(attr_value) else: raise AttributeError(attr_name) def __str__(self): result = "" for attr_name in attr_name_to_attr: result += (attr_name + ": " + str(attr_name_to_attr[attr_name].get()) + ", ") return result return Result()
With data prepared using
name_and_email_lst = [("John"," email1@dot.com "), ("John"," email2@dot.com "), ("Jack"," email3@dot.com "), ("Hack"," email4@dot.com "), ] email = AttrSet() name = AttrSet() for name_str, email_str in name_and_email_lst: email_attr = Attribute(email_str) name_attr = Attribute(name_str) person = create_with_attrs(email=email_attr, name=name_attr) email.add(email_attr, person) name.add(name_attr, person) def print_set(person_set): for person in person_set: print person print
The following sequence of pseudo-SQL fragments yields:
SELECT id FROM email
>>> print_set(email.get()) email: email3@dot.com , name: Jack, email: email4@dot.com , name: Hack, email: email2@dot.com , name: John, email: email1@dot.com , name: John,
SELECT id FROM email WHERE email = " email1@dot.com "
>>> print_set(email.get(" email1@dot.com ")) email: email1@dot.com , name: John,
SELECT id FROM email WHERE email = " email1@dot.com " OR email = " email2@dot.com "
>>> print_set(email.get(" email1@dot.com ", " email2@dot.com ")) email: email1@dot.com , name: John, email: email2@dot.com , name: John,
SELECT id FROM name WHERE name = "John"
>>> print_set(name.get("John")) email: email1@dot.com , name: John, email: email2@dot.com , name: John,
SELECT id FROM name, email WHERE name = "John" AND email = " email1@dot.com "
>>> print_set(name.get("John").intersection(email.get(" email1@dot.com "))) email: email1@dot.com , name: John,
ADD email, name SET email = " jon@dot.com ", name = "Jon"
WHERE id IN
SELECT id FROM email WHERE email = " email1@dot.com "
>>> person = email.get(" email1@dot.com ").pop() >>> person.name = "Jon"; person.email = " jon@dot.com " >>> print_set(email.get()) email: email3@dot.com , name: Jack, email: email4@dot.com , name: Hack, email: email2@dot.com , name: John, email: jon@dot.com , name: Jon,
REMOVE FROM email, name WHERE id =% s
SELECT id FROM email
>>> name.remove(person) >>> email.remove(person) >>> print_set(email.get()) email: email3@dot.com , name: Jack, email: email4@dot.com , name: Hack, email: email2@dot.com , name: John,