【问题标题】:A question regarding string instance uniqueness in python关于python中字符串实例唯一性的问题
【发布时间】:2010-11-12 03:41:49
【问题描述】:

我试图找出python只实例化一次的整数(似乎是-6到256),在这个过程中偶然发现了一些我看不到模式的字符串行为。有时,相等的字符串以不同的方式创建共享相同的 id,有时不共享。这段代码:

A = "10000"
B = "10000"
C = "100" + "00"
D = "%i"%10000
E = str(10000)
F = str(10000)
G = str(100) + "00"
H = "0".join(("10","00"))

for obj in (A,B,C,D,E,F,G,H):
    print obj, id(obj), obj is A

打印:

10000 4959776 真 10000 4959776 真 10000 4959776 真 10000 4959776 真 10000 4959456 假 10000 4959488 假 10000 4959520 假 10000 4959680 假

我什至没有看到这种模式——除了前四个没有明确的函数调用这一事实——但肯定不可能,因为 C 中的“+”例如暗示add 的函数调用。我特别不明白为什么 C 和 G 不同,因为这意味着加法组件的 ID 比结果更重要。

那么,A-D 经历了怎样的特殊处理,使它们成为同一个实例?

【问题讨论】:

  • 你有没有反汇编字节码,看看每一行实际生成了什么?你知道编译时优化是否完成了吗?
  • 起初我对此表示怀疑,因为我从解释器那里得到了相同的结果,但我用 dis 尝试了你的建议。我将结果放在另一个答案中(评论框中没有足够的空间),它使正在发生的事情变得可爱而清晰。谢谢!
  • @Markus:永远不要评论你自己的问题。要么更新问题,要么回答它,这就是你所做的。不要为回答你自己的问题而道歉。如果您更新答案以删除“cmets 空间不足”业务,这将有所帮助。
  • @S.Lott:评论你自己的问题有什么问题,尤其是回复别人的评论?在这种特殊情况下,我觉得这个问题是独立的,所以不想改变它。你让我得到了一个很好的答案,所以我发布了。但是,在您的评论中,我的某些评论是专门针对您的。还有什么更好的地方呢?我意识到这个讨论现在已经无可救药地离题了,但我看不出有其他方法。

标签: python string instance uniqueidentifier


【解决方案1】:

我相信可以在编译时评估的短字符串将被自动实习。在最后一个示例中,无法在编译时评估结果,因为可能会重新定义 strjoin

【讨论】:

    【解决方案2】:

    允许 Python 内联字符串常量; A,B,C,D 实际上是相同的文字(如果 Python 看到一个常量表达式,它会将其视为一个常量)。

    str 实际上是一个类,所以str(whatever) 正在调用这个类的构造函数,它应该产生一个新的对象。这解释了 E、F、G(请注意,它们中的每一个都有不同的标识)。

    至于 H,我不确定,但我会解释这个表达式太复杂,Python 无法确定它实际上是一个常量,所以它计算一个新字符串。

    【讨论】:

    • 完全允许调用不可变类型的构造函数而不是“产生一个新对象”——int(23) is int(23) 是 True,而与 str 相同的是 False——至少在大多数当前实现中;有关此类优化启发式的更多详细信息,请参阅我的长答案(并且不要依赖它们,因为它们可能会在下一个版本中更改!-)。
    • str(aString) 似乎也只是传递字符串,而不是创建一个新字符串。
    • Alex,你是对的,我在这里没有理解可变对象和不可变对象之间的区别。谢谢指正。
    【解决方案3】:

    在语言规范方面,完全允许任何兼容的 Python 编译器和运行时,对于不可变类型的任何实例,创建一个新实例或找到一个相同类型的现有实例,该实例等于所需的值并使用对同一实例的新引用。这意味着在不可变对象之间使用is 或按ID 比较总是不正确的,任何次要版本都可能在此问题上调整或更改策略以增强优化。

    在实现方面,权衡非常明确:尝试重用现有实例可能意味着花费时间(可能浪费)试图找到这样的实例,但如果尝试成功,则节省了一些内存(以及是时候分配和稍后释放保存新实例所需的内存位了)。

    如何解决这些实施折衷问题并不完全显而易见——如果您可以确定启发式方法表明可能找到合适的现有实例并且搜索(即使失败)会很快,那么您可能想要尝试当启发式建议时进行搜索和重用,否则跳过它。

    在您的观察中,您似乎发现了一个特定的 dot-release 实现,它在完全安全、快速和简单的情况下执行少量的窥视孔优化,因此 A 到 D 的分配都归结为与 A 完全相同(但 E 到 F 没有,因为它们涉及命名函数或方法,优化器的作者可能合理地认为这些函数或方法不能 100% 安全地假设语义——如果这样做的话,投资回报率也会很低——所以它们不是窥视孔——优化)。

    因此,A 到 D 重复使用相同的实例归结为 A 和 B 这样做(因为 C 和 D 被优化为完全相同的构造)。

    反过来,这种重用清楚地表明了编译器策略/优化器启发式方法,即同一函数的本地命名空间中不可变类型的相同文字常量被折叠为对函数 .func_code.co_consts 中的一个实例的引用(使用当前 CPython 的术语对于函数和代码对象的属性)——合理的策略和启发式方法,因为在一个函数中重复使用相同的不可变常量文字有点频繁,并且价格只支付一次(在编译时),而优势是多次累积的(每次函数运行时,可能在循环内等)。

    (碰巧这些特定的策略和启发式方法,考虑到它们明显的积极权衡,已经在所有最新版本的 CPython 中普遍存在,我相信 IronPython、Jython 和 PyPy 也是如此;-)。

    如果您打算为 Python 本身或类似语言编写编译器、运行时环境、窥视孔优化器等,这是一个有点值得和有趣的研究。我猜想对内部进行深入研究(当然,最好是许多不同的正确实现,以免专注于特定的怪癖——好在 Python 目前享有至少 4 个独立的生产价值实现,更不用说每个版本都有几个版本!)也可以间接地帮助使一个更好的 Python 程序员成为一个更好的 Python 程序员——但特别重要的是关注语言本身保证的内容,这比你想要的要少在不同的实现中找到共同点,因为现在“恰好”共同的部分(语言规范没有要求如此)可能会在下一个点完全改变发布一个或另一个实现,如果您的生产代码错误地依赖这些细节,那可能会导致令人讨厌的意外;-)。另外——几乎没有必要,甚至特别有帮助,依赖于这样的变量实现细节而不是语言强制的行为(当然,除非你正在编写优化器、调试器、分析器之类的东西;- )。

    【讨论】:

    • 相切但相关:在 Lua 中,字符串不仅是不可变的,而且是唯一的:每个具有相同值的字符串都是同一个对象。这听起来很慢,但 Lua 被称为一种非常快的脚本语言;它使用经过优化的哈希表来做到这一点。它具有 all 字符串相等比较是 O(1) 的伟大特性。字符串相等变成指针比较。
    • 绝对是一个有趣的语言设计选择,感谢提及。
    【解决方案4】:

    回应 S.Lott 关于检查字节码的建议:

    import dis
    def moo():
        A = "10000"
        B = "10000"
        C = "100" + "00"
        D = "%i"%10000
        E = str(10000)
        F = str(10000)
        G = "1000"+str(0)
        H = "0".join(("10","00"))
        I = str("10000")
    
        for obj in (A,B,C,D,E,F,G,H, I):
            print obj, id(obj), obj is A
    moo()
    print dis.dis(moo)
    

    产量:

    10000 4968128 True
    10000 4968128 True
    10000 4968128 True
    10000 4968128 True
    10000 2840928 False
    10000 2840896 False
    10000 2840864 False
    10000 2840832 False
    10000 4968128 True
      4           0 LOAD_CONST               1 ('10000')
                  3 STORE_FAST               0 (A)
    
      5           6 LOAD_CONST               1 ('10000')
                  9 STORE_FAST               1 (B)
    
      6          12 LOAD_CONST              10 ('10000')
                 15 STORE_FAST               2 (C)
    
      7          18 LOAD_CONST              11 ('10000')
                 21 STORE_FAST               3 (D)
    
      8          24 LOAD_GLOBAL              0 (str)
                 27 LOAD_CONST               5 (10000)
                 30 CALL_FUNCTION            1
                 33 STORE_FAST               4 (E)
    
      9          36 LOAD_GLOBAL              0 (str)
                 39 LOAD_CONST               5 (10000)
                 42 CALL_FUNCTION            1
                 45 STORE_FAST               5 (F)
    
     10          48 LOAD_CONST               6 ('1000')
                 51 LOAD_GLOBAL              0 (str)
                 54 LOAD_CONST               7 (0)
                 57 CALL_FUNCTION            1
                 60 BINARY_ADD          
                 61 STORE_FAST               6 (G)
    
     11          64 LOAD_CONST               8 ('0')
                 67 LOAD_ATTR                1 (join)
                 70 LOAD_CONST              12 (('10', '00'))
                 73 CALL_FUNCTION            1
                 76 STORE_FAST               7 (H)
    
     12          79 LOAD_GLOBAL              0 (str)
                 82 LOAD_CONST               1 ('10000')
                 85 CALL_FUNCTION            1
                 88 STORE_FAST               8 (I)
    
     14          91 SETUP_LOOP              66 (to 160)
                 94 LOAD_FAST                0 (A)
                 97 LOAD_FAST                1 (B)
                100 LOAD_FAST                2 (C)
                103 LOAD_FAST                3 (D)
                106 LOAD_FAST                4 (E)
                109 LOAD_FAST                5 (F)
                112 LOAD_FAST                6 (G)
                115 LOAD_FAST                7 (H)
                118 LOAD_FAST                8 (I)
                121 BUILD_TUPLE              9
                124 GET_ITER            
            >>  125 FOR_ITER                31 (to 159)
                128 STORE_FAST               9 (obj)
    
     15         131 LOAD_FAST                9 (obj)
                134 PRINT_ITEM          
                135 LOAD_GLOBAL              2 (id)
                138 LOAD_FAST                9 (obj)
                141 CALL_FUNCTION            1
                144 PRINT_ITEM          
                145 LOAD_FAST                9 (obj)
                148 LOAD_FAST                0 (A)
                151 COMPARE_OP               8 (is)
                154 PRINT_ITEM          
                155 PRINT_NEWLINE       
                156 JUMP_ABSOLUTE          125
            >>  159 POP_BLOCK           
            >>  160 LOAD_CONST               0 (None)
                163 RETURN_VALUE        
    

    所以看起来编译器确实将 A-D 理解为相同的意思,因此它通过只生成一次来节省内存(正如 Alex、Maciej 和 Greg 所建议的那样)。 (添加案例I 似乎只是 str() 意识到它正在尝试从字符串中生成字符串,然后将其传递。)

    谢谢大家,现在清楚多了。

    【讨论】:

      猜你喜欢
      • 2020-03-04
      • 1970-01-01
      • 2012-12-12
      • 2021-07-17
      • 2020-07-25
      • 2011-08-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多