Python не имеет никакой поддержки для клонирования генераторов.
Концептуально это должно быть реализовано, по крайней мере, для CPython. Но практически, оказывается, это очень сложно.
Под крышками генератор в основном представляет собой обертку вокруг рамки стека. *
И рамка объект, по существу, только код объект, указатель инструкции (индекс в этот коде объект), окружающая среда/Глобал встроенных функций/местных жителей, за исключение государственный, и некоторые флаги и отладочная информацию.
И оба типа подвергаются воздействию уровня Python, **, как и все необходимые им биты. Таким образом, это действительно должно быть только вопрос:
- Создание объекта кадра так же, как
g.gi_frame
, но с копией местных жителей вместо первоначальных местных жителей. (Все вопросы на уровне пользователя сводятся к тому, чтобы создать мелкие копии, глубокие копии или один из вышеперечисленных и рекурсивно клонирующих генераторов.)
- Создайте объект-генератор из нового объекта фрейма (и его код и бегущий флаг).
И нет очевидной практической причины, из-за которой невозможно построить объект кадра из его битов, как и для объекта кода или большинства других скрытых встроенных типов.
К сожалению, как оказалось, Python не предоставляет способ создания объекта фрейма. Я думал, что вы можете обойти это, просто используя ctypes.pythonapi
для вызова PyFrame_New
, но первым аргументом является PyThreadState
, который вы определенно не можете создать из Python и не должен быть способен. Таким образом, чтобы сделать эту работу, вы должны либо:
- Воспроизводить все
PyFrame_New
делает, ударяя на структуры С помощью ctypes
или
- вручную создать поддельный
PyThreadState
ударяя на структуры C (которая будет по-прежнему необходимо внимательно прочитать код до PyFrame_New
, чтобы узнать, что вам нужно подделать).
Я думаю, что это еще может быть выполнимо (и я планирую играть с ним, если я придумываю что-нибудь, я буду обновлять Cloning generators пост в моем блоге), но это, безусловно, не будет тривиальности или, конечно, даже отдаленно портативный.
Есть также пара незначительных проблем.
Местные жители подвергаются Python как Dict (назовем ли вы locals()
для вашей собственной, или доступ g.gi_frame.f_locals
для генератора вы хотите клонировать). Под обложками местные жители фактически хранятся в стеке C. *** Вы можете обойти это, используя ctypes.pythonapi
для звонка PyFrame_LocalsToFast
и PyFrame_FastToLocals
. Но dict просто содержит значения, а не клеточные объекты, поэтому при этом тасуляция превратит все нелокальные переменные в локальные переменные в клоне. ****
состояние Исключения подвергается Python как типа/значение/отладочных 3-кортеж, но внутри фрейма есть также заимствовано (не refcounted) ссылка на генератор владеющего (или если значение NULL это не кадр генератора). (The source объясняет, почему.) Итак, ваша функция построения кадра не может пересчитать генератор, или у вас есть цикл и, следовательно, утечка, но он должен пересчитать генератор или у вас есть потенциально оборванный указатель, пока кадр не будет назначен генератор. Очевидный ответ, похоже, заключается в том, чтобы оставить генератор NULL при построении кадра и иметь функцию построения генератора, эквивалентную self.gi_f.f_generator = self; Py_DECREF(self)
.
* Он также сохраняет копию объекта кода фрейма и работает флаг, так что они могут быть доступны после выхода генератора и избавляется от рамы.
** generator
и frame
скрыты от встроенных команд, но они доступны как types.GeneratorType
types.FrameType
. И у них есть docstrings, описания их атрибутов в модуле inspect
и т. Д., Как функции и объекты кода.
*** При компиляции определения функции, компилятор делает список всех местных жителей, которые хранятся в co_varnames
, и превращает каждую переменную ссылку в LOAD_FAST
/STORE_FAST
опкодом с индексом в co_varnames
в качестве аргумента. Когда выполняется вызов функции, объект фрейма хранит указатель стека в f_valuestack
, толкает len(co_varnames)*sizeof(PyObject *)
в стек, а затем LOAD_FAST 0
просто обращается к *f_valuestack[0]
. Замки сложнее; слишком много, чтобы объяснить в комментарии к SO ответ.
**** Я предполагаю, что вы хотите, чтобы клон делил ссылки на закрытие оригинала. Если вы надеялись рекурсивно клонировать все фреймы в стек, чтобы получить новый набор ссылок на привязку к связыванию, это добавляет еще одну проблему: нет способа построить новые объекты ячеек из Python.
Это не так просто, как кажется. Что делать, если генератор использует файл или сетевой поток, который нельзя клонировать? Хотя взгляните на ['itertools.tee'] (https://docs.python.org/3/library/itertools.html#itertools.tee) –
Это дубликат? http://stackoverflow.com/questions/4945155/how-to-clone-a-python-generator-object –
@ColonelThirtyTwo Это может вызвать ошибку для всех, что мне волнует. Он должен работать только для кода, который не преподает ввод или вывод. – PyRulez