2016-05-27 3 views
6

私はオブジェクトのリストを持っています。各オブジェクトは、私は、リスト内のオブジェクトを削除しても、オブジェクトの属性を変更/追加し続けるだろう二つのフィールドオブジェクトのインデックスリストへのpythonic方法

obj1.status = 2 
obj1.timestamp = 19211 

obj2.status = 3 
obj2.timestamp = 14211 

obj_list = [obj1, obj2] 

を持って、例えば私は5
にob1.status変更されることがあり今、私は2枚のdicts

を持っています
dict1 - <status, object> 
dict2 - <timestamp, object> 

リストの要素を変更/削除/挿入するたびにマップが自動的に更新されるように、単純なソリューションを設計するにはどうすればよいですか。私はエレガントで拡張性のあるpythonicソリューションに興味があります。たとえば、将来的には別の属性を簡単に追加してそのことを簡単に説明できるはずです

また、すべての属性値が異なるものとしましょう。たとえば、2つのオブジェクトのステータスが同じではない

+0

オブジェクトがすでに両方を持っているのに、なぜあなたは '<、オブジェクトをタイムスタンプ>' <ステータス、オブジェクト> 'と'の辞書を作成することになり見ています属性? –

+0

高速アクセス用のインデックスが必要です。たとえば、ステータス3のオブジェクトを取得したいとします。 –

+3

複数のオブジェクトのステータスまたはタイムスタンプが似ているとどうなりますか? –

答えて

2

setオブジェクトから構築する必要があることを考えると、すべての属性の正確な重複が存在しないことを前提としてい値を設定するたびに索引を更新してください。索引にはweakrefディクショナリを使用できます。これにより、オブジェクトを削除しても使用しなくなると、索引から自動的に削除されます。

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] 

それはあなたが.name表記を使用して、インデックスにアクセスすることができますが、あなただけの代わりにdictを使用して['name']構文を使用することができますので、私はここにBunchを使用。

+0

ニース!削除は機能していません。要素はリストからのみ削除されるため、オブジェクトへの強い参照はまだメモリに残っています。また、status_mapとtimestamp_mapを作成するのではなく、クラスを属性の入力リストとしてそこからビルドすることができるように、これを一般化するといいでしょう。 –

+0

すべての参照を削除する必要があります。これはリストから削除し、 'del obj1'を実行することを意味します。また、これをPythonのプロンプトから実行している場合、最後に返された値は '_'変数に設定されるため、その変数をリセットするか削除する必要があります。 –

+0

@darkknightええ、あなたはYakymをやって、クラスの属性にするだけで、クラスの外にそれらを作成する必要はありません。 –

2

dictMyObjに設定し、propertyデコレータを使用して更新動作を定義する方法があります。オブジェクトが変更または追加されるたびに、クラスに関連付けられた尊重された辞書に反映されます。

編集:@BrendanAbelが指摘しているように、dictの代わりにweakref.WeakValueDictionaryを使用すると、クラスレベルの辞書からオブジェクトが削除されます。その要素の突然変異を意識するために収集するために

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良い編集ですが、Python 3では必要ありません。 –

+0

Nice!アイテムがリストから削除されても機能しません。また、問題の前提を追加しました。だから__delete_from_classを削除することができます。 –

+0

あなたは正しいです、これはオブジェクトがリストから削除されるときdictsを更新しません。__delete_from_classは、同じオブジェクトが2つの異なる値によって指されていないことを確認するために使用されます。 –

1

、変更が発生したときに通信できる要素と、そのコレクションの間にいくつかの接続がなければなりません。このため、インスタンスをコレクションにバインドするか、コレクションの要素をプロキシして、変更通信が要素のコードに漏れないようにする必要があります。

私が提示しようとしている実装についての注意点、プロキシ方法は、メソッドの内部ではなく直接設定によって属性が変更された場合にのみ機能します。もっと複雑な帳簿システムが必要になるでしょう。

また、それはあなたがオブジェクト上__setattr__へをオーバーライドすることができますインデックスが代わりに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

これはあなたが求めているものかどうかはわかりませんが...

オブジェクト:

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

リスト:aが最初f含み、bは最初h

a = [f] 
b = [h] 

辞書含ま:その値のリストのいずれか一つの項目とそれぞれ

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

d1[1]ff.oneを含むリストaある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) 

ある辞書に

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

d2[1]h

>>> d2 
{1: [<__main__.Foo object at 0x03F59070>]} 
>>> name(d2[1][0]), d2[1][0].one 
('h', 1) 
を含むリストbであります

bにオブジェクトを追加し、それが辞書に

>>> 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) 
関連する問題