【问题标题】:Which types of Python objects are initialized with a reference, and which are not?哪些类型的 Python 对象是用引用初始化的,哪些不是?
【发布时间】:2019-06-07 02:53:29
【问题描述】:

我在 Windows 上的 Python 3.7 中使用 sys.getrefcount。我尝试了以下方法:

>>> import sys
>>> x = "this is an arbitrary string"
>>> sys.getrefcount(x)
2

我了解其中一个引用是x,另一个是sys.getrefcount 内部使用的参数。无论x 初始化为哪种类型,这似乎都有效。但是,当我在通过之前没有分配时,我注意到了一些奇怪的行为:

>>> import sys
>>> sys.getrefcount("arbitrary string")
2
>>> sys.getrefcount(1122334455)
2
>>> sys.getrefcount(1122334455+1)
2
>>> sys.getrefcount(frozenset())
2
>>> sys.getrefcount(set())
1
>>> sys.getrefcount(object())
1
>>> sys.getrefcount([])
1
>>> sys.getrefcount(lambda x: x)
1
>>> sys.getrefcount(range(1122334455))
1
>>> sys.getrefcount(dict())
1
>>> sys.getrefcount(())
8341
>>> sys.getrefcount(tuple())
8340
>>> sys.getrefcount(list("arbitrary string"))
1
>>> sys.getrefcount(tuple("arbitrary string"))
1
>>> sys.getrefcount(("a", "r", "b", "i", "t", "r", "a", "r", "y", " ", "s", "t", "r", "i", "n", "g"))
2

这里发生了什么?似乎不可变类型有两个引用,但可变类型只有一个?为什么看起来有些对象在传递之前已分配,而另一些对象则只有引用作为参数? 这和str/int/tuple实习有关系吗?

编辑:一个更直接的问题:为什么选择像frozenset() 这样的不可变类型在构造时有引用,而像set() 这样的可变类型没有?我孤立地理解为什么您可能会选择保留此全局范围参考,或不全面保留,但为什么会出现差异?

【问题讨论】:

    标签: python reference garbage-collection cpython


    【解决方案1】:

    一个有趣的问题,所以这里是一个有趣的read

    你应该试试getrefcount(2),对我来说它返回了 93,这意味着 CPython 为相同的内存地址保留了 93 个引用,保留了第二个,所以它不必再次分配它,因为它是不可变的完全可以这样做。

    现在让我们尝试两种不同的方法:

    # first
    getrefcount(set()) # returns 1
    
    # second
    s = set()
    getrefcount(s) # returns 2
    

    由于它是可变类型,因此当您创建可变类型 (set()) 时,它的行为会有所不同,它将在内存中分配它,并且只有一个对它的引用,该引用在这一行结束后立即被删除。但是在第二个我们定义变量并分配它时,在计算引用时,我们有s使用的一个和getrefcount函数内部使用的一个。

    在 Python tuples are immutable 中,这就是它返回大量数字的原因,CPython 保留了大量对空元组的引用。

    【讨论】:

    • 我想我的问题的一个更具体的形式:为什么选择像 frozenset() 这样的不可变类型在构造时有引用,而像 set() 这样的可变类型没有?我孤立地理解为什么您可能会选择保留此全局范围的参考或不全面保留,但为什么会出现差异?
    【解决方案2】:

    在了解更多信息后回答我自己的问题。

    区别与 python 字节码对象的格式有关。 sys.getrefcount("arbitrary string")的字节码如下:

    >>> dis.dis('sys.getrefcount("arbitrary string")')
      1           0 LOAD_NAME                0 (sys)
                  2 LOAD_METHOD              1 (getrefcount)
                  4 LOAD_CONST               0 ('arbitrary string')
                  6 CALL_METHOD              1
                  8 RETURN_VALUE
    

    这里,LOAD_CONST 操作码不会从头开始构造一个新字符串,它只是从代码对象的常量元组中加载一个字符串。那个元组就是持有额外引用的东西:

    >>> f = lambda: sys.getrefcount("arbitrary string")
    >>> f.__code__.co_consts
    (None, 'arbitrary_string')
    

    考虑到这一点,一些例子是有意义的:

    >>> import sys
    
    # the string is stored in co_consts
    >>> sys.getrefcount("arbitrary string")
    2
    
    # the integer is store in co_consts
    >>> sys.getrefcount(1122334455)
    2
    
    # this addition is constant-folded and the result is stored in co_consts
    >>> sys.getrefcount(1122334455+1)
    2
    
    # Tuples of constants are folded into one big constant:
    >>> sys.getrefcount(("a", "r", "b", "i", "t", "r", "a", "r", "y", " ", "s", "t", "r", "i", "n", "g"))
    2
    

    同时,以下对象不能存储在 co_consts 中,因为它们必须每次都重新构建,因为它们要么是可变的,要么依赖于必须查找的某个函数的函数调用:

    >>> sys.getrefcount(set()) # construct a new set at call time
    1
    >>> sys.getrefcount(object()) # construct a new object at call time
    1
    >>> sys.getrefcount([]) # construct a new list at call time
    1
    >>> sys.getrefcount(lambda x: x) # construct a new function at call time
    1
    
    # construct a new range object at call time.
    # We could do something dumb like range = abs,
    # so this can't be constant-folded.
    >>> sys.getrefcount(range(1122334455))
    1
    
    >>> sys.getrefcount(dict()) # construct a new dict at call time
    1
    >>> sys.getrefcount(list("arbitrary string")) # construct a new list at call time
    1
    
    # Construct a new tuple at call time.
    # We could do something dumb like tuple=list
    # so this can't be constant-folded.
    >>> sys.getrefcount(tuple("arbitrary string")) 
    1
    

    最后,第三类对象是那些利用某种缓存或保留的对象,当构造一个新对象时,Python 会以某种方式欺骗并给你一个已经存在的对象。

    # There is only one empty frozenset that gets re-used.
    >>> sys.getrefcount(frozenset())
    2
    
    # There is only one empty tuple that gets re-used
    # whenever someone requests an empty tuple
    >>> sys.getrefcount(())
    8341
    
    # The same thing, but that tuple does get stored
    # in co_consts because the name `tuple` could be rebound.
    >>> sys.getrefcount(tuple())
    8340
    

    要验证有关 freezesets 的断言,请注意 一个空的 freezeset 的引用计数增加 新建一个时:

    >>> sys.getrefcount(frozenset())
    2
    >>> x, y, z = frozenset(), frozenset(), frozenset()
    >>> sys.getrefcount(frozenset())
    5
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-12-26
      • 1970-01-01
      • 2019-01-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-08-09
      相关资源
      最近更新 更多