基于属性的随机访问对象集合的Python数据结构

4 投票
4 回答
5843 浏览
提问于 2025-04-15 14:06

我需要一个可以通过每个对象的某个(唯一)属性来查找的对象集合。目前我使用的是字典,把字典的键设置为这个属性。以下是我现在的做法:

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"

不过,这种做法似乎有点麻烦,因为字典的键并不是对属性的引用,而是在赋值时取了键属性的值。这就意味着:

  • 字典的键重复了对象属性中已经存在的信息,
  • 当对象的属性发生变化时,字典的键不会自动更新。

使用列表并遍历对象似乎更低效。

那么,是否有比字典更合适的数据结构,能够让我根据某个对象属性随机访问对象集合呢?

这需要在Python 2.4中运行,因为我在工作中只能用这个版本。

如果还没看出来,我对Python还很陌生。

4 个回答

0

其实,字典(dict)正是你需要的东西。可能让你觉得麻烦的,不是字典本身,而是你构建它的方式。这里有一个小改进,展示了如何使用列表表达式和字典构造器,轻松创建你的查找字典。这还展示了如何创建一种多重映射的字典,可以根据某个可能在多个项目中重复的字段值来查找匹配的项目:

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"]

输出结果是:

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

这里有很多很棒的事情可以做。举个例子,可以让这个类来跟踪所有的内容:

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

需要注意的是,你会遇到更新的问题:如果 key 发生变化,_member_dict 就会不同步。这时候,封装就显得很重要了:要让改变 key 变得几乎不可能,而不更新字典。想了解怎么做到这一点,可以看看 这个教程

5

其实你担心的信息重复并不存在:字典的键和对象的 .key 属性其实都是指向同一个对象的两个引用。

真正的问题是“如果 .key 被重新赋值怎么办”。那么,显然你需要使用一个属性,这个属性可以同时更新所有相关的字典和实例的属性;所以每个对象都必须知道它可能被注册到哪些字典中。理想情况下,你希望使用弱引用来避免循环依赖,但可惜的是,你不能对字典使用 weakref.ref(或代理)。所以在这里,我使用的是普通引用(另一种选择是使用一些特殊的子类,而不是 dict 实例,这样不太方便)。

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)

编辑:如果你想要一个包含所有 Item 实例的集合,那就更简单了,因为你可以把这个集合设为类级别的属性;实际上,它可以是一个 WeakValueDictionary,以避免错误地保持项目存活,如果这是你需要的。也就是说:

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)

现在你可以使用 Item.all['akey']Item.all.get('akey')for akey in Item.all: 等等——所有字典的丰富功能。

撰写回答