【问题标题】:Given a dict iterator, get the dict给定一个字典迭代器,得到字典
【发布时间】:2019-10-02 19:54:37
【问题描述】:

给定一个列表迭代器,您可以通过 pickle 协议找到原始列表:

>>> L = [1, 2, 3]
>>> Li = iter(L)
>>> Li.__reduce__()[1][0] is L
True

给定一个dict迭代器,你怎么能找到原始的dict?我只能找到一种使用 CPython 实现细节的 hacky 方式(通过垃圾收集器):

>>> def get_dict(dict_iterator): 
...     [d] = gc.get_referents(dict_iterator) 
...     return d 
...
>>> d = {}
>>> get_dict(iter(d)) is d
True

【问题讨论】:

  • 迭代器不会公开这一点,因为 API 并不要求它们这样做。这有什么用例?
  • 我可以将用例视为生成器的视图。

标签: python dictionary iterator python-internals


【解决方案1】:

没有 API 可以从迭代器中找到源可迭代对象。这是故意的,迭代器被视为一次性对象;迭代并丢弃。因此,一旦到达终点,他们经常放弃他们的可迭代引用;如果你不能得到更多的元素,那么保留它有什么意义呢?

您在 list 和 dict 迭代器中都看到了这一点,一旦您完成迭代,您发现的 hack 要么产生空对象,要么产生 None。列表迭代器在腌制时使用空列表:

>>> l = [1]
>>> it = iter(l)
>>> it.__reduce__()[1][0] is l
True
>>> list(it)  # exhaust the iterator
[1]
>>> it.__reduce__()[1][0] is l
False
>>> it.__reduce__()[1][0]
[]

并且字典迭代器只是将指向原始字典的指针设置为空,因此之后就没有所指对象了:

>>> import gc
>>> it = iter({'foo': 42})
>>> gc.get_referents(it)
[{'foo': 42}]
>>> list(it)
['foo']
>>> gc.get_referents(it)
[]

你的两个技巧都是这样的:hacks。它们是依赖于实现的,并且可以并且可能会在 Python 版本之间发生变化。目前,使用iter(dictionary).__reduce__() 可以获得与iter, list(copy(self)) 等效的功能,而不是访问字典,因为这被认为是更好的实现,但未来的版本可能会使用完全不同的东西,等等。

对于字典,目前唯一可用的其他选项是使用 ctypes 访问 di_dict pointer in the dictiter struct

import ctypes

class PyObject_HEAD(ctypes.Structure):
    _fields_ = [
        ("ob_refcnt", ctypes.c_ssize_t),
        ("ob_type", ctypes.c_void_p),
    ]

class dictiterobject(ctypes.Structure):
    _fields_ = [
        ("ob_base", PyObject_HEAD),
        ("di_dict", ctypes.py_object),
        ("di_used", ctypes.c_ssize_t),
        ("di_pos", ctypes.c_ssize_t),
        ("di_result", ctypes.py_object),  # always NULL for dictkeys_iter
        ("len", ctypes.c_ssize_t),
    ]

def dict_from_dictiter(it):
    di = dictiterobject.from_address(id(it))
    try:
        return di.di_dict
    except ValueError:  # null pointer
        return None

这与依赖gc.get_referents() 一样多:

>>> d = {'foo': 42}
>>> it = iter(d)
>>> dict_from_dictiter(it)
{'foo': 42}
>>> dict_from_dictiter(it) is d
True
>>> list(it)
['foo']
>>> dict_from_dictiter(it) is None
True

目前,至少在 Python 3.8 及之前的 CPython 版本中,没有其他可用选项。

【讨论】:

    猜你喜欢
    • 2021-02-07
    • 1970-01-01
    • 1970-01-01
    • 2021-09-22
    • 2014-04-10
    • 1970-01-01
    • 2016-12-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多