Python:从集合中获取项目
一般来说,Python 的集合(set)似乎并不是为了通过键来获取项目而设计的。显然,字典(dictionary)就是为了这个目的而存在的。但是,是否有办法在给定一个键的情况下,从集合中找到一个与这个键相等的实例呢?
我知道这正是字典的用途,但我认为有合理的理由想要在集合中做到这一点。假设你定义了一个类似这样的类:
class Person:
def __init__(self, firstname, lastname, age):
self.firstname = firstname
self.lastname = lastname
self.age = age
现在,假设我将要创建大量的 Person
对象,每次创建一个 Person
对象时,我需要确保它不是之前创建的 Person
对象的重复。只要两个 Person
对象的 firstname
相同,就认为它们是重复的,不管其他的实例变量是什么。因此,显而易见的做法是把所有的 Person
对象放进一个集合中,并定义 __hash__
和 __eq__
方法,这样 Person
对象就可以通过它们的 firstname
来比较。
另一种选择是创建一个 Person
对象的字典,并使用单独创建的 firstname
字符串作为键。这样做的缺点是我会重复存储 firstname
字符串。在大多数情况下这并不是个问题,但如果我有 10,000,000 个 Person
对象呢?冗余的字符串存储可能会在内存使用上造成很大的负担。
但是,如果两个 Person
对象比较相等,我需要能够检索到原始对象,以便将其他实例变量(除了 firstname
)合并,这样才能满足业务逻辑的要求。这又让我回到了我的问题:我需要某种方法从集合中检索实例。
有没有办法做到这一点?还是说使用字典是唯一的选择呢?
3 个回答
我觉得你可以在这里找到答案:
是的,你可以这样做:set
是可以被遍历的。不过要注意,这个操作的时间复杂度是 O(n),而字典的操作时间复杂度是 O(1)。
所以,你需要在 速度 和 内存 之间做个权衡。这是个经典的问题。就我个人而言,我会选择优化速度(也就是使用字典),因为在只有 10,000,000 个对象的情况下,内存不会很快就不够用,而且使用字典真的很简单。
至于 firstname
字符串的额外内存消耗:因为在 Python 中字符串是不可变的,所以把 firstname
属性作为键时,不会创建一个新的字符串,而只是复制了它的引用。
在这里,我建议使用字典。把 firstname
这个实例变量当作字典的键使用时,并不会复制它——字典只是会用同一个对象。我觉得字典占用的内存不会比集合多多少。
如果想真正节省内存,可以给你的类添加一个 __slots__
属性。这样可以防止你创建的 10,000,000 个实例每个都有一个 __dict__
属性,这样能节省比字典相比集合可能带来的更多内存。
编辑: 这里有一些数据来支持我的说法。我定义了一个简单的示例类,用来存储一对对随机字符串:
def rand_str():
return str.join("", (chr(random.randrange(97, 123))
for i in range(random.randrange(3, 16))))
class A(object):
def __init__(self):
self.x = rand_str()
self.y = rand_str()
def __hash__(self):
return hash(self.x)
def __eq__(self, other):
return self.x == other.x
在我的机器上,1,000,000 个这个类的实例所占用的内存
random.seed(42)
s = set(A() for i in xrange(1000000))
是 240 MB。如果我给这个类添加了
__slots__ = ("x", "y")
,那么内存使用量就降到了 112 MB。如果我把相同的数据存储在字典中
def key_value():
a = A()
return a.x, a
random.seed(42)
d = dict(key_value() for i in xrange(1000000))
,那么没有 __slots__
时会使用 249 MB,有了 __slots__
后则使用 121 MB。