1

Как указано в документе memoization example в документах decorator, вы не можете использовать подход вложенных функций для реализации memoization, сохраняя при этом подпись функции. Вместо этого, вы должны поднять внутреннюю функцию, и затем создать тривиальную функцию декоратора:Почему модуль декоратора заставляет меня поднять внутреннюю функцию на внешний уровень?

def _memoize(func, *args, **kwargs): 
    # the memoization code 

def memoize(f): 
    f.cache = {} 
    return decorator(_memoize, f) 

Почему я не могу использовать внутреннюю функцию? Или документы вводят в заблуждение, то есть есть возможность использовать внутреннюю функцию с @decorator? Есть ли какая-то практическая причина, основанная на реализации, почему это так, или я действительно вынужден сделать это чужой путь? Я ненавижу вспомогательные функции и хотел бы избежать этого подхода, если это возможно; если есть хак, чтобы заставить его работать (без, конечно, самого написания с нуля), я хотел бы услышать, что это такое.

Следует отметить, что без необходимости инициализации cache или любого другого кода, который не был бы частью внутренней функции, конечно, @decorator работает просто отлично, не используя внешнюю функцию вообще (но затем снова , зачем использовать внутреннюю функцию, если у вас нет кода вне внутренней функции?).

+0

Я не понимаю, что вы подразумеваете под «Я ненавижу вспомогательные функции». '_memoize' - вспомогательная функция, и фактически точная вспомогательная функция _same_, будь то на верхнем уровне или вложенная внутри' memoize'. – abarnert

+0

@abarnert Я понимаю это; Наверное, я хотел сказать, что я ненавижу вспомогательные функции, которые становятся доступными по имени, когда вы делаете «из импорта модуля». Это приводит меня к вопросу: если это тот же самый код, почему меня принуждают сделать это одним способом без возможности другого?Я попытался вернуть «декоратор (внутренний, func)» и т. П. Из внешней функции, действующей как декоратор, и не повезло. Нет никакой разницы, кроме того, если вы импортируете мой модуль, вы можете получить сломанную функцию '_memoize', которая выдает исключение, если вы передадите ему функцию без атрибута' cache'. – 2rs2ts

+1

Ну, есть стандартные способы избежать этого. Часто просто отговаривайте «от импорта модуля». Если это не подходит, укажите явный '__all__'. (Если вы действительно этого захотите, вы можете просто «деблокировать» функцию после того, как вы закончите с ней, или спрячьте ее где-нибудь и т. Д.). Но в этом случае ... атрибуты модуля верхнего уровня, начинающиеся с одного подчеркивания, - это _already_ не импортируется 'из импорта модуля * ', поэтому проблема даже не возникает. – abarnert

ответ

3

Требуя это намеренный выбор дизайн decorator библиотеки, поясняется в документации (курсив в оригинале):

Разница в отношении memoize_uw подхода, который основан на вложенных функций, является что модуль декоратора заставляет вас поднимать внутреннюю функцию на внешнем уровне (плоский лучше, чем вложенный).

Это также объясняется прямо в разделе мотивации Введения:

Например, типичные реализации декораторов включают вложенные функции, и все мы знаем, что плоское лучше, чем вложенные.

Конечно, «плоский лучше, чем вложенный» является частью the Zen of Python. Но вы можете не соглашаться с тем, что оно применимо здесь, или может думать, что какой-то другой принцип его переопределяет.

Если вы не согласны с принципами дизайна библиотеки, вы, вероятно, не будете довольны этим.


Если вы смотрите в the source code, вы можете увидеть, что модуль делает некоторое использование предположения о том, что вы собираетесь проездом в функции верхнего уровня. Например, он копирует func_globals, но не пытается копировать нелокальные ячейки закрытия, и он ожидает, что inspect будет иметь функцию уровня модуля для работы. Во многих случаях нарушение этих предположений не повредит вам. Но если вы настаиваете на этом, вам нужно будет понять код достаточно хорошо, чтобы знать, когда будет причинить вам вред.

+0

Да, это правильно. Я понимаю, что это намеренное дизайнерское решение (хотя я надеюсь, что его можно взломать), и я вижу его обоснование, но я часто вижу, что «плоская лучше, чем вложенная» имеет большее значение, когда вы пытаетесь сбалансировать читаемые уровни отступов и ограничение на 80 символов на линию, поэтому вы видите, что люди обескураживают множество вложенных структур управления. Но, если это так важно, зачем закрывать? – 2rs2ts

+0

@ 2rs2ts: Zen - это не набор абсолютных правил, а набор принципов, которые должны быть сбалансированы. Бывают случаи, когда закрытие или создание отдельного функционального объекта для каждого вызова или что-то еще является очевидным способом делать то, что вы хотите сделать, и в этих случаях очевидным способом сделать это является вложение функции. Так сделай это. Но когда вы не получаете никакой пользы от гнездования, не делайте этого. – abarnert

+0

@ 2rs2ts: Чтобы воспользоваться одним из следующих примеров: когда очевидный дизайн использует закрытие, вы всегда можете обойтись без одного по-разному - с помощью класса, изменчивых значений по умолчанию или даже глобального сопоставления с соответствующими ключами (вместе с связкой дополнительного кода). И это позволит избежать гнездования. Но это также нарушит «Простой лучше, чем сложный» и «Красиво лучше, чем уродливый» и TOOWDTI, и «Если реализация трудно объяснить». Итак, вы балансируете принципы и решаете пойти с гнездом вместо сложного и хакерского беспорядка, чтобы избежать этого. – abarnert