Well, another way may be to implement the following:
Attr
is an abstraction for a "value". We need this since there is no "assignment overloading" in Python (simple get / set paradigm is used as the cleanest alternative). Attr
also acts as an "Observable".
AttrSet
is an "Observer" for Attr
s, which tracks their value changes while effectively acting as an Attr
-to-whatever (person
in our case) dictionary.
create_with_attrs
is a factory producing what looks like a named-tuple, forwarding attribute access via supplied Attr
s, so that person.name = "Ivan"
effectively yields person.name_attr.set("Ivan")
and makes the AttrSet
s observing this person
's name
appropriately rearrange their internals.
The 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 the data prepared with
name_and_email_lst = [("John","[email protected]"),
("John","[email protected]"),
("Jack","[email protected]"),
("Hack","[email protected]"),
]
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 pseudo-SQL snippet sequence gives:
SELECT id FROM email
>>> print_set(email.get())
email: [email protected], name: Jack,
email: [email protected], name: Hack,
email: [email protected], name: John,
email: [email protected], name: John,
SELECT id FROM email WHERE email="[email protected]"
>>> print_set(email.get("[email protected]"))
email: [email protected], name: John,
SELECT id FROM email WHERE email="[email protected]" OR email="[email protected]"
>>> print_set(email.get("[email protected]", "[email protected]"))
email: [email protected], name: John,
email: [email protected], name: John,
SELECT id FROM name WHERE name="John"
>>> print_set(name.get("John"))
email: [email protected], name: John,
email: [email protected], name: John,
SELECT id FROM name, email WHERE name="John" AND email="[email protected]"
>>> print_set(name.get("John").intersection(email.get("[email protected]")))
email: [email protected], name: John,
UPDATE email, name SET email="[email protected]", name="Jon"
WHERE id IN
SELECT id FROM email WHERE email="[email protected]"
>>> person = email.get("[email protected]").pop()
>>> person.name = "Jon"; person.email = "[email protected]"
>>> print_set(email.get())
email: [email protected], name: Jack,
email: [email protected], name: Hack,
email: [email protected], name: John,
email: [email protected], name: Jon,
DELETE FROM email, name WHERE id=%s
SELECT id FROM email
>>> name.remove(person)
>>> email.remove(person)
>>> print_set(email.get())
email: [email protected], name: Jack,
email: [email protected], name: Hack,
email: [email protected], name: John,