【问题标题】:Python: Identical strings (or numbers) with unique ids?Python:具有唯一 ID 的相同字符串(或数字)?
【发布时间】:2015-05-28 23:38:43
【问题描述】:

Python 进行了出色的优化,但我有一个案例想解决它。似乎对于小数字和字符串,python 会自动将多个对象合并为一个。例如:

>>> a = 1
>>> b = 1
>>> id(a) == id(b)
True
>>> a = str(a)
>>> b = str(b)
>>> id(a) == id(b)
True
>>> a += 'foobar'
>>> b += 'foobar'
>>> id(a) == id(b)
False
>>> a = a[:-6]
>>> b = b[:-6]
>>> id(a) == id(b)
True

我有一个案例,我根据它们的 Python id 比较对象。除了我遇到少量的少数情况外,这非常有效。有谁知道如何关闭特定字符串和整数的优化?类似于 anti-intern() 的东西?

【问题讨论】:

  • 文字值是在 CPython 中实现的,因此它们具有相同的 id 值。

标签: python string


【解决方案1】:

您根本不应该将这些对象视为不同的对象。如果不修改和重新编译 Python,就无法关闭此行为,并且它适用于哪些特定对象可能会更改,恕不另行通知。

【讨论】:

  • 如果我们不能依赖 id,那么我们也不应该在 Python 中使用 'is'。
  • @aviso:对于这些对象,您确实也不应该使用is
  • 'is' 用于比较两个对象是否相同,这正是本练习的重点。
【解决方案2】:

如果不重新编译您自己的 CPython 版本,您将无法关闭它。

但是,如果您想拥有相同小整数的“单独”版本,您可以通过维护与对象关联的自己的 id(例如uuid4)来实现。

由于整数和字符串是不可变的,所以没有明显的理由这样做——如果你根本无法修改对象,你不应该关心你是拥有“原始”还是副本,因为没有用处——它可以产生任何影响的情况。

相关:How to create the int 1 at two different memory locations?

【讨论】:

  • 为每个对象创建一个 uuid 不允许分析引用,这是练习的重点。
【解决方案3】:

当然,它可以完成,但它从来都不是一个好主意:

# 
Z =1

class MyString(string):
    def __init__(self, *args):
        global Z
        super(MyString, 
                  self).__init__(*args)
        self.i = Z
        Z += 1

>>> a = MyString("1")
>>> b = MyString("1")
>>> a is b
False

顺便说一句,要比较对象是否具有相同的id,只需使用a is b 而不是id(a)==id(b)

【讨论】:

  • 这似乎不是一个可扩展的解决方案。
【解决方案4】:

Python documentation on id()

返回对象的“身份”。这是一个整数,保证在其生命周期内对于该对象是唯一且恒定的。生命周期不重叠的两个对象可能具有相同的 id() 值。

CPython 实现细节:这是对象在内存中的地址。

所以它保证是唯一的,它必须是一种判断两个变量是否绑定到同一个对象的方法。

a comment on StackOverflow here 中,Alex Martelli 说 CPython 实现不是权威的 Python,Python 的其他正确实现可以并且确实在某些方面表现不同 - 而Python Language Reference (PLR) 是 Python 最接近的东西到一个明确的规范。

the PLR section on objects 中的内容大致相同:

每个对象都有一个标识、一个类型和一个值。对象的身份一旦创建就永远不会改变;您可能会将其视为对象在内存中的地址。 ‘is’ 运算符比较两个对象的身份; id() 函数返回一个表示其身份的整数(当前实现为其地址)。

语言参考并没有说它保证是唯一的。它还说(重新:对象的生命周期):

对象永远不会被显式销毁;但是,当它们变得无法访问时,它们可能会被垃圾收集。实现可以推迟垃圾收集或完全忽略它——只要没有收集到仍然可访问的对象,如何实现垃圾收集是实现质量的问题。

和:

CPython 实现细节:CPython 目前使用引用计数方案,对循环链接的垃圾进行(可选)延迟检测,该方案在大多数对象变得无法访问时立即收集它们,但不能保证收集包含循环引用的垃圾。有关控制循环垃圾收集的信息,请参阅 gc 模块的文档。其他实现的行为不同,CPython 可能会改变。当对象变得无法访问时,不要依赖于立即完成对象(例如:始终关闭文件)。

这实际上不是一个答案,我希望这最终会是一个决定性的结果。但是现在我已经引用和引用了,我不想删除它。

我会改变你的前提:python will automatically collapse multiple objects into one. - 不,它不会,它们从来都不是多个对象,它们不可能是,因为它们具有相同的 id()

如果 id() 是 Python 关于两个对象相同还是不同的明确答案,那么您的前提是不正确的 - 这不是优化,它是 Python 世界观的基本组成部分。

【讨论】:

  • 一个对象,多个名称
  • 感谢您提供的所有信息。如果 Python 折叠多个对象,我们可以同意不同意。我很确定这正是 C 级别发生的事情。
【解决方案5】:

这个版本解释了 wim 对未来更激进的拘禁的担忧。它将使用更多内存,这就是我最初丢弃它的原因,但可能是更多的未来证明。

>>> class Wrapper(object):
...     def __init__(self, obj):
...             self.obj = obj

>>> a = 1
>>> b = 1
>>> aWrapped = Wrapper(a)
>>> bWrapped = Wrapper(b)
>>> aWrapped is bWrapped
False
>>> aUnWrapped = aWrapped.obj
>>> bUnwrapped = bWrapped.obj
>>> aUnWrapped is bUnwrapped
True

或者类似pickle答案的版本(wrap + pickle = wrapple):

class Wrapple(object):
    def __init__(self, obj):
        self.obj = obj

    @staticmethod
    def dumps(obj):
        return Wrapple(obj)

    def loads(self):
        return self.obj

aWrapped = Wrapple.dumps(a)
aUnWrapped = Wrapple.loads(a)

【讨论】:

    【解决方案6】:

    好吧,鉴于没有人发布有用的回复,我会告诉你我最终做了什么。

    首先,给可能有一天会读到这篇文章的人一些友好的建议。不建议将其用于正常使用,因此,如果您正在考虑使用它,请问问自己是否有充分的理由。有充分的理由,但它们很少见,如果有人说没有,他们只是想得不够努力。

    最后,我只是在所有对象上使用了 pickle.dumps() 并传递了输出而不是真实的对象。另一方面,我检查了 id,然后使用 pickle.loads() 来恢复对象。这个解决方案的优点是它适用于所有类型,包括 None 和 Booleans。

    >>> a = 1
    >>> b = 1
    >>> a is b
    True
    >>> aPickled = pickle.dumps(a)
    >>> bPickled = pickle.dumps(b)
    >>> aPickled is bPickled
    False
    >>> aUnPickled = pickle.loads(aPickled)
    >>> bUnPickled = pickle.loads(bPickled)
    >>> aUnPickled is bUnPickled
    True
    >>> aUnPickled
    1
    

    【讨论】:

    • 但是aPickledAgain = pickle.dumps(a); aPickled is aPickledAgain 也给出了False。这是 wim 建议的一个非常笨拙的版本。
    • 是的,完全符合预期。重点是遵循对象的引用,而不是它们的值。腌制该对象使其在原本不会的地方变得独一无二。如果你再次腌制它,你要求另一个独特的对象。这不是 wim 建议的实现,因为他也关心值而不是对象。
    • 同意这很笨拙,因为您依赖的假设是pickle.dumps 生成的那些字节串不会被实习(实现不能保证,并且您的代码将来可能会中断没有任何警告)
    • 那么提供一个更好的解决方案。你的不处理新的和任意的对象类型。我明白你关于拘禁的观点。在泡菜之前我有一个不同的想法,可以更好地处理拘留。我会把它作为另一个答案。
    猜你喜欢
    • 2015-02-13
    • 2012-06-21
    • 2016-01-17
    • 2015-09-08
    • 2023-03-10
    • 1970-01-01
    • 2020-07-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多