2016-05-27 12 views
6

Ho una lista di oggetti. Ogni oggetto ha due campimodo pitone per indicizzare l'elenco degli oggetti

obj1.status = 2 
obj1.timestamp = 19211 

obj2.status = 3 
obj2.timestamp = 14211 

obj_list = [obj1, obj2] 

io continuare ad aggiungere/eliminare gli oggetti nella lista e anche la modifica degli attributi di oggetti, ad esempio posso cambiare ob1.status a 5.
Ora ho due dicts

dict1 - <status, object> 
dict2 - <timestamp, object> 

Come progettare una soluzione semplice in modo che ogni volta che modifico/cancelli/inserisca elementi nell'elenco, le mappe vengano aggiornate automaticamente. Sono interessato a una soluzione pitonica che sia elegante ed estensibile. Per esempio, in futuro dovrei essere in grado di aggiungere facilmente un altro attributo e dettare anche quello

Anche per semplicità, assumiamo che tutti gli attributi abbiano un valore diverso. Ad esempio, nessun due oggetti avrà lo stesso stato

+0

Perché si creare dizionari di '' e '' quando l'oggetto ha già sia gli attributi? –

+0

Desidero gli indici per l'accesso rapido, ad esempio voglio ottenere oggetto di stato 3 –

+3

Cosa succede se più oggetti hanno lo stato o il timestamp simile? –

risposta

2

Si potrebbe ignorare il __setattr__ sugli oggetti per aggiornare gli indici ogni volta che si impostano i valori. È possibile utilizzare un dizionario weakref per gli indici in modo che quando si eliminano gli oggetti e non vengano più utilizzati, vengono automaticamente rimossi dagli indici.

import weakref 
from bunch import Bunch 


class MyObject(object): 

    indexes = Bunch() # Could just use dict() 

    def __init__(self, **kwargs): 
     super(MyObject, self).__init__() 
     for k, v in kwargs.items(): 
      setattr(self, k, v) 

    def __setattr__(self, name, value): 
     try: 
      index = MyObject.indexes[name] 
     except KeyError: 
      index = weakref.WeakValueDictionary() 
      MyObject.indexes[name] = index 
     try: 
      old_val = getattr(self, name) 
      del index[old_val] 
     except (KeyError, AttributeError): 
      pass 
     object.__setattr__(self, name, value) 
     index[value] = self 


obj1 = MyObject(status=1, timestamp=123123) 
obj2 = MyObject(status=2, timestamp=2343) 


print MyObject.indexes.status[1] 
print obj1.indexes.timestamp[2343] 
obj1.status = 5 
print obj2.indexes['status'][5] 

Ho usato un Bunch qui perché permette di accedere gli indici utilizzando la notazione .name, ma si potrebbe utilizzare un dict invece e utilizzare la sintassi ['name'].

+0

Nice! La cancellazione non funziona L'elemento viene eliminato solo dall'elenco, quindi un riferimento forte all'oggetto è ancora in memoria. Sarebbe anche bello generalizzare questo in modo che invece di creare status_map e timestamp_map, la classe potesse prendere come input l'elenco di attributi e costruire da lì. –

+0

Dovresti cancellare tutti i riferimenti. Ciò significa eliminarlo dalla lista e fare 'del obj1'. Inoltre, se lo fai dal prompt di python, l'ultimo valore restituito viene impostato sulla variabile '_', quindi dovrai resettarlo o cancellarlo. –

+0

@darkknight Sì, si potrebbe fare Yakym e basta renderli attributi di classe, in modo da non doverli creare al di fuori della classe. –

2

Un approccio qui sarebbe quello di creare un livello di classe dict per MyObj e definire il comportamento di aggiornamento utilizzando property decoratore. Ogni volta che un oggetto viene modificato o aggiunto, si riflette nei dizionari rispettati associati alla classe.

Modifica: come @BrendanAbel sottolinea, utilizzando weakref.WeakValueDictionary al posto di dict si gestisce l'eliminazione degli oggetti dai codici di classe.

from datetime import datetime 
from weakref import WeakValueDictionary 

DEFAULT_TIME = datetime.now() 


class MyObj(object): 
    """ 
    A sample clone of your object 
    """ 
    timestamps = WeakValueDictionary() 
    statuses = WeakValueDictionary() 

    def __init__(self, status=0, timestamp=DEFAULT_TIME): 
     self._status = status 
     self._timestamp = timestamp 

     self.status  = status 
     self.timestamp = timestamp 

    def __update_class(self): 
     MyObj.timestamps.update({self.timestamp: self}) 
     MyObj.statuses.update({self.status: self}) 

    def __delete_from_class(self): 
     maybe_self = MyObj.statuses.get(self.status, None) 
     if maybe_self is self is not None: 
      del MyObj.statuses[self.status] 

     maybe_self = MyObj.timestamps.get(self.timestamp, None) 
     if maybe_self is self is not None: 
      del MyObj.timestamps[self.timestamp] 

    @property 
    def status(self): 
     return self._status 

    @status.setter 
    def status(self, val): 
     self.__delete_from_class() 
     self._status = val 
     self.__update_class() 

    @property 
    def timestamp(self): 
     return self._timestamp 

    @timestamp.setter 
    def timestamp(self, val): 
     self.__delete_from_class() 
     self._timestamp = val 
     self.__update_class() 

    def __repr__(self): 
     return "MyObj: status={} timestamp={}".format(self.status, self.timestamp) 


obj1 = MyObj(1) 
obj2 = MyObj(2) 
obj3 = MyObj(3) 

lst = [obj1, obj2, obj3] 

# In [87]: q.lst 
# Out[87]: 
# [MyObj: status=1 timestamp=2016-05-27 13:43:38.158363, 
# MyObj: status=2 timestamp=2016-05-27 13:43:38.158363, 
# MyObj: status=3 timestamp=2016-05-27 13:43:38.158363] 

# In [88]: q.MyObj.statuses[1] 
# Out[88]: MyObj: status=1 timestamp=2016-05-27 13:43:38.158363 

# In [89]: q.MyObj.statuses[1].status = 42 

# In [90]: q.MyObj.statuses[42] 
# Out[90]: MyObj: status=42 timestamp=2016-05-27 13:43:38.158363 

# In [91]: q.MyObj.statuses[1] 
# --------------------------------------------------------------------------- 
# KeyError         Traceback (most recent call last) 
# <ipython-input-91-508ab072bfc4> in <module>() 
# ----> 1 q.MyObj.statuses[1] 

# KeyError: 1 
+0

@darkknight buona modifica, anche se non è richiesta per python 3. –

+0

Nice! Non funziona quando l'elemento viene cancellato dalla lista. Aggiunta anche un'ipotesi in questione. Quindi forse __delete_from_class può essere rimosso. –

+0

Sei corretto, questo non aggiorna i dicts quando un oggetto viene rimosso dalla lista.Il __delete_from_class viene utilizzato per assicurarsi che lo stesso oggetto non sia puntato da due valori diversi. –

1

Per una collezione di essere a conoscenza di mutazione dei suoi elementi, ci deve essere un collegamento tra gli elementi e che la raccolta in grado di comunicare quando cambiamenti accadere. Per questo motivo, è necessario associare un'istanza a una raccolta o proxy degli elementi della raccolta in modo che la comunicazione delle modifiche non si diffonda nel codice dell'elemento.

Una nota sull'implementazione che ho intenzione di presentare, il metodo di proxy funziona solo se gli attributi sono modificati dall'impostazione diretta, non all'interno di un metodo. Sarebbe quindi necessario un sistema di contabilità più complesso.

Inoltre, si presume che non esisteranno esatto duplicato di tutti gli attributi, dato che si richiedono gli indici essere costruiti fuori set oggetti invece di list

from collections import defaultdict 

class Proxy(object): 
    def __init__(self, proxy, collection): 
     self._proxy = proxy 
     self._collection = collection 

    def __getattribute__(self, name): 
     if name in ("_proxy", "_collection"): 
      return object.__getattribute__(self, name) 
     else: 
      proxy = self._proxy 
      return getattr(proxy, name) 

    def __setattr__(self, name, value): 
     if name in ("_proxy", "collection"): 
      object.__setattr__(self, name, value) 
     else: 
      proxied = self._proxy 
      collection = self._collection 
      old = getattr(proxied, name) 
      setattr(proxy, name, value) 
      collection.signal_change(proxied, name, old, value) 


class IndexedCollection(object): 
    def __init__(self, items, index_names): 
     self.items = list(items) 
     self.index_names = set(index_names) 
     self.indices = defaultdict(lambda: defaultdict(set)) 

    def __len__(self): 
     return len(self.items) 

    def __iter__(self): 
     for i in range(len(self)): 
      yield self[i]  

    def remove(self, obj): 
     self.items.remove(obj) 
     self._remove_from_indices(obj) 

    def __getitem__(self, i): 
     # Ensure consumers get a proxy, not a raw object 
     return Proxy(self.items[i], self) 

    def append(self, obj): 
     self.items.append(obj) 
     self._add_to_indices(obj) 

    def _add_to_indices(self, obj): 
      for indx in self.index_names: 
       key = getattr(obj, indx) 
       self.indices[indx][key].add(obj) 

    def _remove_from_indices(self, obj): 
      for indx in self.index_names: 
       key = getattr(obj, indx) 
       self.indices[indx][key].remove(obj) 

    def signal_change(self, obj, indx, old, new): 
      if indx not in self.index_names: 
       return 
      # Tell the container to update its indices for a 
      # particular attribute and object 
      self.indices[indx][old].remove(obj) 
      self.indices[indx][new].add(obj) 
-1

Non sono sicuro se questo è quello che stai chiedendo ma ...

oggetti:

import operator 
class Foo(object): 
    def __init__(self): 
     self.one = 1 
     self.two = 2 

f = Foo() 
f.name = 'f' 
g = Foo() 
g.name = 'g' 
h = Foo() 
h.name = 'h' 

name = operator.attrgetter('name') 

Liste: a inizialmente contiene f e b contiene inizialmente h

a = [f] 
b = [h] 

dizionari: ciascuno con una voce cui valore è una delle liste

d1 = {1:a} 
d2 = {1:b} 

d1[1] è la lista a quali contiene f e f.one è 1

>>> d1 
{1: [<__main__.Foo object at 0x03F4CA50>]} 
>>> name(d1[1][0]) 
'f' 
>>> name(d1[1][0]), d1[1][0].one 
('f', 1) 

cambiando f.one è visto nel dizionario

>>> f.one = '?' 
>>> name(d1[1][0]), d1[1][0].one 
('f', '?') 
>>> 

d2[1] è la lista che contiene bh

>>> d2 
{1: [<__main__.Foo object at 0x03F59070>]} 
>>> name(d2[1][0]), d2[1][0].one 
('h', 1) 

Aggiungere un oggetto a b ed è visto nel dizionario

>>> b.append(g) 
>>> b 
[<__main__.Foo object at 0x03F59070>, <__main__.Foo object at 0x03F4CAF0>] 
>>> d2 
{1: [<__main__.Foo object at 0x03F59070>, <__main__.Foo object at 0x03F4CAF0>]} 
>>> name(d2[1][1]), d2[1][1].one 
('g', 1)