Как написать декоратор без синтаксического сахара в питоне?

Этот вопрос довольно специфичен, и я считаю, что есть много подобных вопросов, но не совсем таких.

Я пытаюсь понять синтаксический сахар. Я понимаю это так, что по определению код всегда можно написать в более подробной форме, но сахар существует для того, чтобы людям было легче с ним обращаться. Так что всегда есть способ написать синтаксический сахар «без сахара», так сказать?

Имея это в виду, как именно вы пишете декоратор без синтаксического сахара? Я понимаю, что это в основном так:

# With syntactic sugar
@decorator
def foo():
    pass
    
# Without syntactic sugar
def foo():
    pass
foo = decorator(foo)

Кроме PEP 318

Текущий синтаксис

Текущий синтаксис декораторов функций, реализованный в Python 2.4a2, выглядит следующим образом:

@dec2
@dec1
def func(arg1, arg2, ...):
    pass

Это эквивалентно:

def func(arg1, arg2, ...):
    pass
func = dec2(dec1(func))

без промежуточного присвоения переменной func. (выделено мной)

В примере, который я привел выше, как обычно объясняется синтаксический сахар, есть промежуточное присваивание. Но как работает синтаксический сахар без промежуточного присваивания? Лямбда-функция? Но я также думал, что они могут быть только одной строкой? Или имя декорируемой функции изменено? Похоже, что это может конфликтовать с другим методом, если пользователь случайно создал его с тем же именем. Но я не знаю, поэтому и спрашиваю.

Чтобы привести конкретный пример, я думаю о том, как определяется свойство. Поскольку при определении метода установки свойства он не может работать, если метод установки определен, поскольку это уничтожит свойство.

class Person:
    def __init__(self, name):
        self.name = name

    @property
    def name(self):
        return self._name
    # name = property(name)
    # This would work
    
    @name.setter
    def name(self, value):
        self._name = value.upper()
    # name = name.setter(name)
    # This would not work as name is no longer a property but the immediately preceding method 

Это делается внутри компилятора, поэтому он не обязательно должен соответствовать реальному коду Python. Просто нужно, чтобы был такой же результат.

Barmar 31.03.2023 01:46

Если вы хотите написать это самостоятельно, вы можете использовать «эквивалентный» код, который он показывает, я не думаю, что это может иметь какие-либо негативные последствия.

Barmar 31.03.2023 01:47

Итак, чтобы было ясно, синтаксический сахар НЕ обязательно соответствует фактическому коду Python? Компилятор позаботится об этом? Это мое замешательство, поскольку я думал, что по определению то, что отделяет синтаксический сахар от обычного кода, сахар в конечном итоге всегда может быть разбит на код, даже если этот код может быть многословным и трудным для чтения.

geckels1 31.03.2023 01:50

Эквивалент отлично работает для обычных декораторов. Это не работает со свойствами, как я пытался продемонстрировать. Работа со свойствами - это то, что вызвало у меня вопрос.

geckels1 31.03.2023 01:51

Это верно. Обычно это просто используется как удобный способ объяснить, что делает синтаксис. За исключением систем макросов, это не описывает фактическую реализацию.

Barmar 31.03.2023 01:52

Спасибо! Это точно отвечает на мой вопрос.

geckels1 31.03.2023 01:55

Я думаю, это объясняет это для свойств, но они реализованы как класс, который определяет метод .setter(). См. docs.python.org/3/library/functions.html#property

Barmar 31.03.2023 01:55

Так что да, если бы вы использовали property без синтаксиса декоратора, вам пришлось бы назвать функцию установки по-другому, чтобы не перезаписать атрибут определением функции: def name...; name = property(name); def namesetter...; name = name.setter(namesetter). Но тогда было бы разумнее просто определить геттер и сеттер и просто вызвать property один раз, а не придерживаться формы: def namegetter...; def namesetter...; name = property(namegetter, namesetter).

Amadan 31.03.2023 02:11

Синтаксический сахар - это, по сути, способ написать что-то более удобным для программиста способом, но не добавляющий выразительности языку; если бы он был удален из языка, вы все равно могли бы реализовать ту же функциональность. Будут ли переменные иметь одинаковые имена или нет, это не сделает язык слабее. Иногда трансформация бывает еще более радикальной: JavaScript async/await — это синтаксический сахар для промисов, которые, в свою очередь, являются сахаром для обратных вызовов; но преобразование кода на основе async/await в код на основе обещаний или обратных вызовов потребует полной реструктуризации.

Amadan 31.03.2023 02:21
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
2
9
77
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий
def func(arg1, arg2, ...):
    pass
func = dec2(dec1(func))

В примере [...] есть промежуточное присваивание. Но как синтаксический сахар работает без промежуточного присваивания?

На самом деле, версия «несинтаксического сахара», как вы ее называете, не совсем совпадает с использованием синтаксиса декоратора с @decorator:

Как вы заметили, при использовании нотации @ начальное имя функции никогда не присваивается переменной: единственное присвоение, которое имеет место, - это разрешенный декоратор.

Так:

@deco
def func():
    ...

То, что присваивается func в области globals(), — это значение, возвращаемое вызовом deco.

Пока в:

def func():
    ...
func = deco(func)

Сначала func назначается необработанной функции, и так же, как выполняется строка func=deco(func), первая затеняется декорированным результатом.

Те же самые яблоки для каскадных декораторов: только конечный результат самого верхнего декоратора всегда присваивается имени переменной.

И, кроме того, имя, используемое при использовании синтаксиса @, взято из исходного кода - имя, используемое в операторе def: если один из декораторов изменит атрибут функции __name__, это не повлияет на присвоенное имя для украшенная функция.

Эти различия являются просто деталями реализации и вытекают из того, как все работает — я не уверен, есть ли они в спецификациях языка, но для тех, кто имеет определенное представление о языке, (1) они кажутся настолько естественными, что никто не будет осмеливаются реализовать его по-другому, и (2) они на самом деле почти не имеют значения - но для кода, который будет отслеживать выполнение программы (отладчик или какой-либо код, использующий возможности аудита языка (https://docs .python.org/3/library/audit_events.html )).

Несмотря на то, что этого нет в других языковых спецификациях, обратите внимание, однако, что разница в том, что синтаксис декоратора не выполняет промежуточное присваивание, записана в PEP 318. В отсутствие других ссылок, в PEP есть закон:

Это эквивалентно: [пример без синтаксиса @], хотя и без промежуточного создания переменной с именем func.

Для полноты картины стоит отметить, что начиная с Python 3.10 (возможно, 3.9) синтаксическое ограничение, которое ограничивало, какие выражения могут использоваться в качестве декораторов после того, как @ было снято, заменяет текст PEP: любое допустимое выражение, которое оценивается как вызываемое, может использовать сейчас.

что с имуществом?

class Person:
    ...
    @property
    def name(self):
        ...
    
    @name.setter
    def name(self, value):
        ...

Что происходит в этом примере, так это то, что name.setter является вызываемым, вызываемым с помощью метода установки (def name(self, value):), определенного ниже, как обычно, но происходит то, что он возвращает объект свойства. (Не то же самое, что @name — новое свойство, но для понимания того, что происходит, оно может даже возвращать тот же объект).

Таким образом, этот код эквивалентен:

class Person:
    ...
    def name(self):
        ...
    name = property(name)  # Creates a property with the getter only
    
    def name_setter(self, value):  # Indeed: this can't be called simply `name`: it would override the property created above
         ...

    name = name.setter(name_setter)  # Creates a new property, copying what already was set in the previous "name" and adding a setter.
    del name_setter  # optionally: removes the explicit setter from the class namespace, as it will always be called via the property. 

На самом деле, хотя свойство было создано до синтаксиса декоратора (IRCC, в Python 2.2 — синтаксис декоратора появился в Python 2.4), оно изначально не использовалось в качестве декоратора. Способ, который использовался для определения свойств в Python 2.2, был следующим:

class Person:
    ...
    def name_getter(self):
        ...
    def name_getter(self):
        ...
    name = property(name_getter, name_setter)

    del name_getter, name_setter # optional

Только в Python 2.5 (или более поздней версии) они сделали умные методы ".setter", ".getter" и ".deleter" для свойств, чтобы их можно было полностью определить с помощью синтаксиса декоратора.

Обратите внимание, что для свойств только с геттером @property будет работать, начиная с Python 2.3, так как он просто принимает единственный параметр, предоставленный синтаксисом декоратора, в качестве геттера. Но впоследствии нельзя было расширить сеттер и детерсер.

Вещь, не связанная с исходной функцией, упоминается в самой близкой вещи Python к спецификации языка. См. документацию по модели данных, в частности, «за исключением того, что исходная функция временно не привязана к имени func».

user2357112 27.05.2023 21:29

Вы можете использовать модуль dis (python3 -m dis Person.py), чтобы увидеть, как это реализовано в CPython.

Сначала в стек помещается (встроенное) имя property, а затем объект кода, полученный в результате принудительного выполнения тела оператора def name.

  5          16 LOAD_NAME                4 (property)

  6          18 LOAD_CONST               2 (<code object name at 0x108d19960, file "tmp.py", line 5>)

Затем объект кода превращается в функцию, но эта функция еще не привязана к имени.

             20 MAKE_FUNCTION            0

Наконец, функция вызывается property, и результирующий экземпляр привязывается к имени name.

  5          22 PRECALL                  0
             26 CALL                     0

  6          36 STORE_NAME               5 (name)

Затем мы делаем то же самое для сеттера. Однако вместо того, чтобы помещать в стек имя property, мы помещаем в стек результат вычисления name.setter.

 11          38 LOAD_NAME                5 (name)
             40 LOAD_ATTR                6 (setter)

Затем мы обрабатываем новую функцию name как и раньше: загружаем объект кода и создаем функцию, но еще не привязывая ее к какому-либо имени. (Обратите внимание, что на этом этапе нам не нужно старое значение имени name, но мы еще и не перезаписываем его.)

 12          50 LOAD_CONST               3 (<code object name at 0x108c173c0, file "tmp.py", line 11>)
             52 MAKE_FUNCTION            0

Вызовите связанный метод для новой функции и привяжите результат обратно к name, наконец, заменив старое значение.

 11          54 PRECALL                  0
             58 CALL                     0

 12          68 STORE_NAME               5 (name)

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