Учитывая итератор dict, получите dict - PullRequest
7 голосов
/ 02 октября 2019

Учитывая итератор списка, вы можете найти исходный список с помощью протокола Pickle:

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

Учитывая итератор dict, как вы можете найти исходный dict? Я мог найти только хакерский способ, используя детали реализации CPython (через сборщик мусора):

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

1 Ответ

6 голосов
/ 04 октября 2019

Нет API для поиска исходного итерируемого объекта от итератора. Это сделано намеренно, итераторы рассматриваются как одноразовые объекты;повторять и отбрасывать. Таким образом, они часто отбрасывают свою повторяемую ссылку , как только достигают конца;какой смысл держать его, если вы все равно не можете получить больше элементов?

Вы видите это и в итераторах списка, и в dict, найденные хаки выдают либо пустые объекты, либо 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)
[]

Оба ваших хака - это просто хаки. Они зависят от реализации и могут и, вероятно, будут меняться между выпусками Python. В настоящее время использование iter(dictionary).__reduce__() дает вам эквивалент iter, list(copy(self)), а не доступ к словарю, потому что это считается лучшей реализацией, но в будущих версиях может использоваться совсем другое и т. Д.

Для словарей единственноедругой доступный на данный момент вариант - получить доступ к указателю di_dict в dictiter struct с помощью ctypes:

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

На данный момент, по крайней мере, в версиях CPython до Python 3.8 включительно другие опции недоступны.

...