【问题标题】:How can I create a circular reference of tuples?如何创建元组的循环引用?
【发布时间】:2019-11-09 20:14:48
【问题描述】:

由于历史原因(阅读:可怕的type(t) == tuple 检查),我发现自己需要将圆形图冻结为tuple 对象的集合。显然,这并不理想:

>>> head = ("head", None)
>>> a = ("a", ("b", ("c", head)))
>>> head[1] = a
Traceback (most recent call last):
  File "<pyshell#3>", line 1, in <module>
    head[1] = a
TypeError: 'tuple' object does not support item assignment

然而,我并不是真正的 TypeErrors 的忠实信徒,我怀疑通过特定于实现的骇客技术可以做到这一点。

  • 如何在不冒越界或导致其他 C 未定义行为的风险的情况下生成这种怪物?
  • 垃圾收集器的循环依赖释放部分能否应对这种情况?

【问题讨论】:

  • 显然有些人在 ctypes 上取得了成功(参见stackoverflow.com/q/6111843/674039),但它总是对我来说是段错误。而且我不知道hash(a) 应该如何为您的假设冻结圆形图存储在元组中工作,它可能会陷入无限循环。为什么不直接使用适当的图形库(例如 networkX),并在需要时定义图形/节点序列化器?
  • @wim 为什么不直接使用list?它实际上不是表示图形,而是表示状态图。这是一段糟糕的代码,应该被烧掉,但现在还不能。我正在将它切碎并尽可能将其送入熔炉。 (不过,您关于hash(a) 的观点是有效的;这实际上可能无法实现,具体取决于代码使用tuple 的用途。)
  • 哦,天哪。求求你,找一些其他可怕的黑客。 sed 's/type(t) == tuple/True/' 怎么样?
  • 发挥创意。覆盖type() 和特殊情况下t 的返回值。加油,我相信你。
  • @JohnKugelmansupportsMonica 上次我尝试修改__builtins__,结果真的很糟糕。但我想它不会比这样做更更糟。实际上,将__builtins__.__dict__ 的类型动态修改为带有空__slots__dict 子类可能更安全,实现__getitem__ 并仅为可怕的模块替换type 的实现……我想我已经在某个地方找到了那个代码。

标签: python-3.x tuples cpython circular-reference


【解决方案1】:

然而,我并不是真正相信 TypeErrors,并且怀疑通过特定于实现的骇客技术可以做到这一点。

很遗憾,你是对的:

from ctypes import Structure, c_ssize_t, c_void_p, py_object, pythonapi

pythonapi.Py_DecRef.argtypes = py_object,


def mutable(tup):
    # We are generating this class dynamically because the size of ob_item
    # varies according to the size of the given tuple
    class PyTupleObject(Structure):
        _fields_ = [('ob_refcnt', c_ssize_t),
                    ('ob_type', c_void_p),
                    ('ob_size', c_ssize_t),
                    ('ob_item', py_object * len(tup))]

        @classmethod
        def from_tuple(cls, tup):
            instance = cls.from_address(id(tup))
            # Save a reference to tup on the instance, as we are using it directly from memory
            # and don't want it to be garbage collected
            instance.original = tup
            return instance

        def __setitem__(self, idx, val):
            # Replacing a value in self.ob_item doesn't decref the old value but does indref the new value
            pythonapi.Py_DecRef(self.ob_item[idx])
            self.ob_item[idx] = val

        def __getitem__(self, idx):
            return self.ob_item[idx]

        def __iter__(self):
            return iter(self.ob_item)

        def __len__(self):
            return len(self.ob_item)

        def __contains__(self, val):
            return val in self.ob_item

    return PyTupleObject.from_tuple(tup)


if __name__ == '__main__':
    tup = (None,)
    mut_tup = mutable(tup)
    mut_tup[0] = tup
    print(tup is tup[0]) # Outputs "True"

我怎样才能在不冒超出界限或导致其他 C 未定义行为的情况下产生这种怪物?

我们通过将成员 ob_item 定义为 py_object * len(tup) 来防止越界访问。

垃圾收集器的循环依赖释放部分可以处理这样的事情吗?

不!元组应该是不可变的,因此不应该对它们自己进行循环引用。这就是为什么他们没有实现tp_clear 方法,python 垃圾收集器使用该方法来打破引用循环并收集所涉及的垃圾。更多详情here

【讨论】:

  • 警告:在结果元组上调用hash,或者在集合中或作为字典键使用它,会导致堆栈溢出。
  • 这不是每次运行mutable(tup) 时都会创建一个新的PyTupleObject 类吗? (是的,这绝对是这段代码最大的问题,而且根本不存在。)
  • 确实如此,因为结构的大小根据元组的长度而变化。我没有找到创建可变大小结构类的方法:)
猜你喜欢
  • 2022-09-22
  • 2016-05-08
  • 2018-07-27
  • 1970-01-01
  • 2018-09-28
  • 1970-01-01
  • 2019-09-27
  • 1970-01-01
  • 2021-09-03
相关资源
最近更新 更多