给定字典迭代器,获取字典

wim*_*wim 6 python dictionary iterator python-internals

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

>>> L = [1, 2, 3]
>>> Li = iter(L)
>>> Li.__reduce__()[1][0] is L
True
Run Code Online (Sandbox Code Playgroud)

给定dict迭代器,如何找到原始dict?我只能使用CPython实现细节(通过垃圾收集器)找到一种骇人听闻的方式:

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

Mar*_*ers 6

没有API可从迭代器中找到源可迭代对象。这是有意的,迭代器被视为一次性对象。迭代并丢弃。这样的话,一旦到达终点,他们通常会放弃其可迭代的参考。如果仍然无法获得更多元素,保留它又有什么意义呢?

您可以在列表迭代器和字典迭代器中看到这一点,发现的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]
[]
Run Code Online (Sandbox Code Playgroud)

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

>>> import gc
>>> it = iter({'foo': 42})
>>> gc.get_referents(it)
[{'foo': 42}]
>>> list(it)
['foo']
>>> gc.get_referents(it)
[]
Run Code Online (Sandbox Code Playgroud)

您的两个hacks都是:hacks。它们取决于实现,并且可能并且可能会在Python版本之间进行更改。目前,使用iter(dictionary).__reduce__()可以等效于iter, list(copy(self))而不是访问字典,因为这被认为是更好的实现,但是将来的版本可能会使用完全不同的东西,依此类推。

对于字典,当前唯一可用的其他选项是使用ctypes 访问struct中di_dict指针dictiter

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
Run Code Online (Sandbox Code Playgroud)

这和依赖一样是一种hack 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
Run Code Online (Sandbox Code Playgroud)

目前,至少在CPython版本(包括python 3.8以上)中,没有其他可用选项。