Pertanyaan Struktur data Python untuk kumpulan objek dengan akses acak berdasarkan atribut


Saya membutuhkan koleksi objek yang dapat dilihat oleh atribut tertentu (unik) yang umum untuk masing-masing objek. Saat ini saya menggunakan penugasan yang menugaskan kunci kamus ke atribut. Berikut ini contoh dari apa yang saya miliki sekarang:

class Item():
    def __init__(self, uniq_key, title=None):
        self.key = uniq_key
        self.title = title

item_instance_1 = Item("unique_key1", title="foo")
item_instance_2 = Item("unique_key3", title="foo")
item_instance_3 = Item("unique_key2", title="foo")

item_collection = {
        item_instance_1.key: item_instance_1,
        item_instance_2.key: item_instance_2,
        item_instance_3.key: item_instance_3
        }

item_instance_1.key = "new_key"

Sekarang ini tampaknya solusi yang agak rumit, karena kuncinya bukanlah referensi ke atribut tetapi mengambil nilai atribut kunci pada tugas, yang berarti bahwa:

  • kunci kamus menduplikasi informasi yang sudah ada dalam bentuk atribut objek dan
  • ketika atribut objek diubah, tombol kamus tidak diperbarui.

Menggunakan daftar dan iterasi melalui objek tampaknya lebih tidak efisien.

Jadi, apakah ada struktur data yang lebih pas daripada dict untuk kasus khusus ini, kumpulan objek yang memberi saya akses acak berdasarkan atribut objek tertentu?

Ini perlu bekerja dengan Python 2.4 karena itulah yang saya terjebak dengan (di tempat kerja).

Jika belum jelas, saya baru mengenal Python.


4
2017-09-05 16:15


asal


Jawaban:


Sebenarnya tidak ada duplikasi informasi yang Anda takuti: kunci dict, dan objeknya .key atribut, hanya dua referensi untuk objek yang sama persis.

Satu-satunya masalah sebenarnya adalah "bagaimana jika .key akan dipindahtugaskan ". Kalau begitu, jelas Anda harus menggunakan properti yang memperbarui semua diktat yang relevan serta atribut instance; sehingga setiap objek harus tahu semua dikt di mana itu dapat diresmikan. Idealnya seseorang akan ingin menggunakan referensi lemah untuk tujuannya, untuk menghindari ketergantungan melingkar, tetapi, sayangnya, Anda tidak dapat mengambil weakref.ref (atau proxy) ke dikte. Jadi, saya menggunakan referensi normal di sini, sebagai gantinya (alternatifnya tidak digunakan dict contoh, tetapi mis. beberapa subkelas khusus - tidak berguna).

def enregister(d, obj):
  obj.ds.append(d)
  d[obj.key] = obj

class Item(object):
    def __init__(self, uniq_key, title=None):
        self._key = uniq_key
        self.title = title
        self.ds = []

    def adjust_key(self, newkey):
        newds = [d for d in self.ds if self._key in d]
        for d in newds:
          del d[self._key]
          d[newkey] = self
        self.ds = newds
        self._key = newkey

    def get_key(self):
        return self._key

    key = property(get_key, adjust_key)

Edit: jika Anda ingin satu koleksi dengan SEMUA contoh Item, itu lebih mudah, karena Anda dapat membuat koleksi atribut tingkat-kelas; memang itu bisa menjadi WeakValueDictionary untuk menghindari kekeliruan menjaga barang tetap hidup, jika itu yang Anda butuhkan. Yaitu.:

class Item(object):

    all = weakref.WeakValueDictionary()

    def __init__(self, uniq_key, title=None):
        self._key = uniq_key
        self.title = title
        # here, if needed, you could check that the key
        # is not ALREADY present in self.all
        self.all[self._key] = self

    def adjust_key(self, newkey):
        # "key non-uniqueness" could be checked here too
        del self.all[self._key]
        self.all[newkey] = self
        self._key = newkey

    def get_key(self):
        return self._key

    key = property(get_key, adjust_key)

Sekarang Anda bisa menggunakannya Item.all['akey'], Item.all.get('akey'), for akey in Item.all:, dan seterusnya - semua fungsi yang kaya dari dicts.


5
2017-09-05 16:44



Ada sejumlah hal hebat yang dapat Anda lakukan di sini. Salah satu contoh adalah membiarkan kelas melacak segalanya:

class Item():
    _member_dict = {}
    @classmethod
    def get_by_key(cls,key):
        return cls._member_dict[key]
    def __init__(self, uniq_key, title=None):
        self.key = uniq_key
        self.__class__._member_dict[key] = self
        self.title = title

>>> i = Item('foo')
>>> i == Item.get_by_key('foo')
True

Catatan Anda akan mempertahankan masalah pembaruan: jika key perubahan, _member_dict tidak sinkron. Di sinilah enkapsulasi akan berguna: membuatnya (praktis) tidak mungkin untuk berubah key tanpa memperbarui kamus. Untuk tutorial bagus tentang cara melakukannya, lihat tutorial ini.


2
2017-09-05 16:44



Nah, dict benar-benar apa yang Anda inginkan. Apa yang mungkin merepotkan bukanlah perintah itu sendiri, tetapi cara Anda membangunnya. Berikut ini adalah sedikit peningkatan pada contoh Anda, menunjukkan cara menggunakan ekspresi daftar dan konstruktor dikt untuk dengan mudah membuat teks pencarian Anda. Ini juga menunjukkan cara membuat jenis dikt multimap, untuk mencari item yang cocok dengan nilai bidang yang dapat diduplikasi di seluruh item:

class Item(object):
    def __init__(self, **kwargs):
        self.__dict__.update(kwargs)
    def __str__(self):
        return str(self.__dict__)
    def __repr__(self):
        return str(self)

allitems = [
    Item(key="red", title="foo"),
    Item(key="green", title="foo"),
    Item(key="blue", title="foofoo"),
    ]

# if fields are unique
itemByKey = dict([(i.key,i) for i in allitems])

# if field value can be duplicated across items
# (for Python 2.5 and higher, you could use a defaultdict from 
# the collections module)
itemsByTitle = {}
for i in allitems:
    if i.title in itemsByTitle:
        itemsByTitle[i.title].append(i)
    else:
        itemsByTitle[i.title] = [i]



print itemByKey["red"]
print itemsByTitle["foo"]

Cetakan:

{'key': 'red', 'title': 'foo'}
[{'key': 'red', 'title': 'foo'}, {'key': 'green', 'title': 'foo'}]

0
2017-09-05 16:41



Mengedit untuk memperbaiki masalah yang saya miliki - yang disebabkan oleh parameter "collection = dict ()" default saya (*bonk*).  Sekarang, setiap panggilan ke fungsi akan mengembalikan kelas dengan koleksi sendiri sebagaimana dimaksud - ini untuk kenyamanan jika lebih dari satu koleksi tersebut harus diperlukan. Juga menempatkan koleksi di kelas dan hanya mengembalikan kelas, bukan dua secara terpisah dalam tupel seperti sebelumnya. (Membiarkan kontainer default di sini sebagai dict (), tapi itu bisa diubah ke Alex's WeakValueDictionary, yang tentu saja sangat keren.)

def make_item_collection(container = None):
    ''' Create a class designed to be collected in a specific collection. '''
    container = dict() if container is None else container
    class CollectedItem(object):
        collection = container
        def __init__(self, key, title=None):
            self.key = key
            CollectedItem.collection[key] = self
            self.title = title
        def update_key(self, new_key):
            CollectedItem.collection[
                new_key] = CollectedItem.collection.pop(self.key)
            self.key = new_key
    return CollectedItem

# Usage Demo...

Item = make_item_collection()
my_collection = Item.collection

item_instance_1 = Item("unique_key1", title="foo1")
item_instance_2 = Item("unique_key2", title="foo2")
item_instance_3 = Item("unique_key3", title="foo3")

for k,v in my_collection.iteritems():
    print k, v.title

item_instance_1.update_key("new_unique_key")

print '****'
for k,v in my_collection.iteritems():
    print k, v.title

Dan inilah hasilnya dengan Python 2.5.2:

unique_key1 foo1
unique_key2 foo2
unique_key3 foo3
****
new_unique_key foo1
unique_key2 foo2
unique_key3 foo3

0
2017-09-05 17:08