python: словарь дилемма: как правильно индексировать объекты на основе атрибута - PullRequest
4 голосов
/ 21 февраля 2010

первый пример:

дано несколько объектов Person с различные атрибуты (имя, ссн, телефон, адрес электронной почты, номер кредитной карты и т. д.)

Теперь представьте следующее простое сайт:

  1. использует адрес электронной почты человека в качестве уникального имени для входа
  2. позволяет пользователям редактировать свои атрибуты (включая адрес электронной почты)

если бы на этом сайте было множество пользователей, тогда имеет смысл хранить Персона объекты в словаре проиндексированы адрес электронной почты, для быстрого человека поиск при входе в систему.

однако, когда адрес электронной почты человека редактируется, а затем словарь ключ для этот человек должен быть изменен как Что ж. это немного противно

я ищу предложения по решению общей проблемы:

дано множество сущностей с общим аспектом. этот аспект используется как для быстрого доступа к объектам, так и в пределах функциональности каждого объекта. где должен быть размещен аспект:

  1. внутри каждого объекта (не подходит для быстрого доступа)
  2. только индекс (не подходит для функциональности каждого объекта)
  3. как внутри каждого объекта, так и в качестве индекса (дубликаты данных / ссылки)
  4. где-то еще / как-то иначе

проблема может быть расширена, скажем, если мы хотим использовать несколько индексов для индексации данных (ssn, номер кредитной карты и т. Д.). в конечном итоге мы можем получить кучу таблиц SQL.

я ищу что-то со следующими свойствами (и больше, если вы можете думать о них):

# create an index on the attribute of a class
magical_index = magical_index_factory(class, class.attribute)
# create an object
obj = class() 
# set the object's attribute
obj.attribute= value
# retrieve object from using attribute as index
magical_index[value] 
# change object attribute to new value
obj.attribute= new_value 
# automagically object can be retrieved using new value of attribute
magical_index[new_value]
# become less materialistic: get rid of the objects in your life
del obj
# object is really gone
magical_index[new_value]
KeyError: new_value

Я хочу, чтобы объект, индексы, все хорошо и без проблем играли друг с другом.

пожалуйста, предложите соответствующие шаблоны проектирования

примечание: приведенный выше пример - это просто пример. пример, используемый для изображения общей проблемы. поэтому, пожалуйста, предоставьте общие решения (конечно, вы можете продолжать использовать пример при объяснении своего общего решения)

Ответы [ 2 ]

3 голосов
/ 21 февраля 2010

Учтите это.

class Person( object ):
    def __init__( self, name, addr, email, etc. ):
        self.observer= []
        ... etc. ...
    @property
    def name( self ): return self._name
    @name.setter
    def name( self, value ): 
        self._name= value
        for observer in self.observedBy: observer.update( self )
    ... etc. ...

Этот атрибут observer реализует Observable , который уведомляет Observers об обновлениях. Это список наблюдателей, которые должны быть уведомлены об изменениях.

Каждый атрибут обернут со свойствами. Использование Descriptors нам, вероятно, лучше, потому что это может сохранить повторение уведомления наблюдателя.

class PersonCollection( set ):
    def __init__( self, *args, **kw ):
        self.byName= collections.defaultdict(list)
        self.byEmail= collections.defaultdict(list)
        super( PersonCollection, self ).__init__( *args, **kw )
    def add( self, person ):
        super( PersonCollection, self ).append( person )
        person.observer.append( self )
        self.byName[person.name].append( person )
        self.byEmail[person.email].append( person )
    def update( self, person ):
        """This person changed.  Find them in old indexes and fix them."""
        changed = [(k,v) for k,v in self.byName.items() if id(person) == id(v) ]
        for k, v in changed:
            self.byName.pop( k )
        self.byName[person.name].append( person )
        changed = [(k,v) for k,v in self.byEmail.items() if id(person) == id(v) ]
        for k, v in changed:
            self.byEmail.pop( k )
        self.byEmail[person.email].append( person)

    ... etc. ... for all methods of a collections.Set.

Используйте collection.ABC для получения дополнительной информации о том, что должно быть реализовано.

http://docs.python.org/library/collections.html#abcs-abstract-base-classes

Если вы хотите «универсальную» индексацию, то ваша коллекция может быть параметризована с именами атрибутов, и вы можете использовать getattr для получения этих именованных атрибутов из базовых объектов.

class GenericIndexedCollection( set ):
    attributes_to_index = [ ] # List of attribute names
    def __init__( self, *args, **kw ):
        self.indexes = dict( (n, {}) for n in self.attributes_to_index ]
        super( PersonCollection, self ).__init__( *args, **kw )
    def add( self, person ):
        super( PersonCollection, self ).append( person )
        for i in self.indexes:
            self.indexes[i].append( getattr( person, i )

Примечание. Чтобы правильно эмулировать базу данных, используйте набор, а не список. Таблицы базы данных (теоретически) являются наборами. На практике они неупорядочены, и индекс позволит базе данных отклонять дубликаты. Некоторые СУБД не отклоняют повторяющиеся строки, потому что - без индекса - это слишком дорого для проверки.

0 голосов
/ 23 февраля 2010

Ну, другим способом может быть реализация следующего:

  1. Attr - абстракция для «значения». Нам это нужно, поскольку в Python нет «перегрузки присваивания» (простая парадигма get / set используется в качестве самой чистой альтернативы). Attr также действует как "Наблюдаемый".

  2. AttrSet - это «Наблюдатель» в течение Attr с, который отслеживает изменения их значений, в то же время эффективно действуя как словарь Attr-к-чему-либо (person в нашем случае).

  3. create_with_attrs - это фабрика, производящая нечто похожее на именованный кортеж, перенаправляющее доступ к атрибуту через предоставленные Attr с, так что person.name = "Ivan" эффективно возвращает person.name_attr.set("Ivan") и заставляет AttrSet s наблюдать это person name соответствующим образом переставляют свои внутренние органы.

код (проверено):

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()

С данными, подготовленными с

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

следующая последовательность фрагментов псевдо-SQL дает:

ВЫБЕРИТЕ идентификатор из электронной почты

>>> 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,

ВЫБЕРИТЕ ИД ИЗ электронной почты, ГДЕ email = "email1@dot.com"

>>> print_set(email.get("email1@dot.com"))
email: email1@dot.com, name: John,

ВЫБЕРИТЕ идентификатор из электронной почты, ГДЕ электронная почта = "email1@dot.com" ИЛИ электронная почта = "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,

ВЫБЕРИТЕ идентификатор ОТ ИМЕНИ ГДЕ имя = "Джон"

>>> print_set(name.get("John"))
email: email1@dot.com, name: John,
email: email2@dot.com, name: John,

ВЫБЕРИТЕ ИД ИЗ ИМЕНИ, электронная почта ГДЕ имя = "Джон" И электронная почта = "email1@dot.com"

>>> print_set(name.get("John").intersection(email.get("email1@dot.com")))
email: email1@dot.com, name: John,

ОБНОВЛЕНИЕ электронной почты, имя SET email = "jon@dot.com", имя = "Jon"

ГДЕ ИДЕНТИФИКАТОР

ВЫБЕРИТЕ идентификатор из электронной почты, ГДЕ 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,

УДАЛИТЬ ИЗ электронной почты, имя ГДЕ id =% s

ВЫБЕРИТЕ идентификатор из электронной почты

>>> 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,
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...