Почему __code__ для функции (Python) изменен

В предыдущем вопросе вчера, в комментариях, я узнал, что в python __code__ атрибут функции изменчив. Поэтому я могу написать код следующим образом

def foo():
    print "Hello"

def foo2():
    print "Hello 2"

foo()
foo.__code__ = foo2.__code__
foo()

Выход

Hello
Hello 2

Я попробовал поиск в Google, но либо потому, что нет информации (я очень сомневаюсь в этом), либо ключевое слово (__code__) нелегко найти для поиска, я не смог найти для этого прецедент.

Не похоже, что "потому что большинство вещей в Python являются изменяемыми" также является разумным ответом, поскольку другие атрибуты функций - __closure__ и __globals__ - явно доступны для чтения (из Objects/funcobject.c):

static PyMemberDef func_memberlist[] = {
    {"__closure__",   T_OBJECT,     OFF(func_closure),
     RESTRICTED|READONLY},
    {"__doc__",       T_OBJECT,     OFF(func_doc), PY_WRITE_RESTRICTED},
    {"__globals__",   T_OBJECT,     OFF(func_globals),
     RESTRICTED|READONLY},
    {"__module__",    T_OBJECT,     OFF(func_module), PY_WRITE_RESTRICTED},
    {NULL}  /* Sentinel */
};

Почему __code__ можно записывать, тогда как другие атрибуты доступны только для чтения?

Ответы

Ответ 1

Дело в том, что большинство вещей в Python изменяемы. Итак, реальный вопрос: почему __closure__ и __globals__ нет?

Ответ сначала кажется простым. Обе эти вещи являются контейнерами для переменных, которые могут понадобиться функции. Сам объект кода не переносит с ним свои закрытые и глобальные переменные; он просто знает, как получить их от функции. Он извлекает фактические значения из этих двух атрибутов при вызове функции.

Но сами области измерения изменяемы, поэтому этот ответ неудовлетворителен. Нам нужно объяснить, почему изменение этих вещей, в частности, могло бы сломать материал.

Для __closure__ мы можем посмотреть на его структуру. Это не отображение, а набор ячеек. Он не знает имена закрытых переменных. Когда объект кода просматривает закрытую переменную, он должен знать свою позицию в кортеже; они совпадают друг с другом с помощью co_freevars, который также доступен только для чтения. И если кортеж имеет неправильный размер или нет кортежа вообще, этот механизм ломается, вероятно, яростно (читайте: segfaults), если базовый код C не ожидает такой ситуации. Принуждение C-кода для проверки типа и размера кортежа - это ненужная занятая работа, которая может быть устранена, если атрибут доступен только для чтения. Если вы попытаетесь заменить __code__ на что-то, принимающее другое количество свободных переменных, вы получите сообщение об ошибке, поэтому размер всегда прав.

В случае __globals__ объяснение становится менее очевидным, но я буду размышлять. Механизм поиска области предполагает, что всегда будет иметь доступ к глобальному пространству имен. Действительно, байт-код может быть жестко закодирован, чтобы перейти прямо к глобальному пространству имен, если компилятор может доказать, что никакое другое пространство имен не будет иметь переменную с конкретное имя. Если глобальное пространство имен было внезапно None или каким-либо другим объектом, не связанным с отображением, код C мог бы снова сильно опуститься. Опять же, делая код, выполняющий ненужные проверки типов, будет пустой тратой циклов процессора.

Другая возможность состоит в том, что (обычно объявленные) функции заимствуют ссылку в глобальное пространство имен модуля, и создание атрибута, доступного для записи, вызовет ссылку посчитайте, чтобы перепутаться. Я мог представить себе этот дизайн, но я не уверен, что это отличная идея, поскольку функции могут быть сконструированы явно с объектами, чьи времена жизни могут быть короче, чем у модуля-владельца, и они должны быть специально обрезаны.