Почему __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 мог бы, опять же, сильно себя вести. Опять же, заставить код выполнять ненужные проверки типов было бы пустой тратой циклов ЦП.

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

Другие вопросы по тегам