【问题标题】:Python: Retrieve items from a setPython:从集合中检索项目
【发布时间】:2011-05-12 14:48:46
【问题描述】:

一般来说,Python 集合似乎并不是为按键检索项目而设计的。这显然是字典的用途。但是无论如何,给定一个键,您可以从一个等于该键的集合中检索一个实例吗?

再一次,我知道这正是字典的用途,但据我所知,有正当的理由想要用一个集合来做这件事。假设你有一个类定义如下:

class Person:
   def __init__(self, firstname, lastname, age):
      self.firstname = firstname
      self.lastname = lastname
      self.age = age

现在,假设我要创建大量 Person 对象,并且每次创建 Person 对象时,我都需要确保它不是之前的 Person 对象的副本。如果Person 具有相同的firstname,则认为它们与另一个Person 重复,而不管其他实例变量如何。所以很自然的做法是将所有Person 对象插入到一个集合中,并定义__hash____eq__ 方法,以便Person 对象通过它们的firstname 进行比较。

另一种选择是创建一个包含Person 对象的字典,并使用单独创建的firstname 字符串作为键。这里的缺点是我会复制firstname 字符串。在大多数情况下,这并不是一个真正的问题,但如果我有 10,000,000 个Person 对象怎么办?就内存使用而言,冗余字符串存储真的会开始增加。

但如果两个Person 对象比较相等,我需要能够检索原始对象,以便可以以业务逻辑所需的方式合并其他实例变量(firstname 除外)。这让我回到了我的问题:我需要一些方法来从 set 检索实例。

有没有办法做到这一点?还是使用字典是这里唯一真正的选择?

【问题讨论】:

  • “Python 集似乎不是为按键检索项目而设计的”。这是一个定义问题。集合没有键。集合中的每个项目都是它自己的密钥。根据定义,“带键的集合”是字典。考虑到定义看起来很清楚,我不确定这个问题是否有意义。

标签: python python-3.x set


【解决方案1】:

我肯定会在这里使用字典。重用firstname 实例变量作为字典键不会复制它——字典只会使用相同的对象。我怀疑字典会比集合使用更多的内存。

要真正节省内存,请将__slots__ 属性添加到您的类中。这将防止 10,000,000 个实例中的每一个实例都具有__dict__ 属性,这将比dict 的潜在开销比set 节省更多的内存。

编辑:一些数据支持我的主张。我定义了一个存储随机字符串对的愚蠢示例类:

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

这使用 249 MB 不带 __slots__ 和 121 MB 带 __slots__

【讨论】:

  • 据我了解,在 CPython 内部集合 are 字典,它们只是将 null 作为存储值。
  • @kindall:也许在某些时候集合是字典,但在当前版本的 Python 中,集合有自己的 C data type 和自己的 C implementation
【解决方案2】:

是的,您可以这样做:可以迭代 set。但请注意,这是一个 O(n) 操作,而不是 dict 的 O(1) 操作。

因此,您必须权衡 速度内存。这是一个经典。我个人会在这里进行优化(即使用字典),因为只有 10,000,000 个对象,内存不会很快变短,而且使用字典真的很容易。

至于firstname字符串的额外内存消耗:由于字符串在Python中是不可变的,因此将firstname属性分配为键不会创建新字符串,而只是复制引用。

【讨论】:

  • 我明白了。令人讨厌的是,虽然速度与内存是一个典型的权衡,但在这种特殊情况下,没有固有的需要进行权衡。一个集合可以很容易地进行 O(1) 检索,但它根本不是由于语言限制。
  • 为什么要进行 O(1) 检索?在一般情况下,您将如何实施?您可能可以将集合视为简并字典,并将值用作键本身。但是检索你已经拥有的东西是毫无意义的,不是吗?
  • 您写道“由于字符串在 Python 中是不可变的,因此将 firstname 属性指定为键不会创建新字符串,而只是复制引用。”这不是 Python 中不变性的一个特性,而是 Python 如何处理名称和赋值的一个特性。您可以拥有一个可变对象并将其用作键,Python 不会创建该对象的新实例。
  • @Steven Rumbalski:对。我在那里相当不精确(即错误)。但是,在分配字符串时,很高兴知道您是否获得了副本,或者如果没有,您的字符串不会从代码中的其他地方发生变异。感谢您指出这一点!
【解决方案3】:

我想你会在这里得到答案:

Moving Beyond Factories in Python

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-10-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-09-07
    • 1970-01-01
    • 2013-11-14
    • 1970-01-01
    相关资源
    最近更新 更多