Этот вопрос довольно специфичен, и я считаю, что есть много подобных вопросов, но не совсем таких.
Я пытаюсь понять синтаксический сахар. Я понимаю это так, что по определению код всегда можно написать в более подробной форме, но сахар существует для того, чтобы людям было легче с ним обращаться. Так что всегда есть способ написать синтаксический сахар «без сахара», так сказать?
Имея это в виду, как именно вы пишете декоратор без синтаксического сахара? Я понимаю, что это в основном так:
# 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? Компилятор позаботится об этом? Это мое замешательство, поскольку я думал, что по определению то, что отделяет синтаксический сахар от обычного кода, сахар в конечном итоге всегда может быть разбит на код, даже если этот код может быть многословным и трудным для чтения.
Эквивалент отлично работает для обычных декораторов. Это не работает со свойствами, как я пытался продемонстрировать. Работа со свойствами - это то, что вызвало у меня вопрос.
Это верно. Обычно это просто используется как удобный способ объяснить, что делает синтаксис. За исключением систем макросов, это не описывает фактическую реализацию.
Спасибо! Это точно отвечает на мой вопрос.
Я думаю, это объясняет это для свойств, но они реализованы как класс, который определяет метод .setter()
. См. docs.python.org/3/library/functions.html#property
Так что да, если бы вы использовали property
без синтаксиса декоратора, вам пришлось бы назвать функцию установки по-другому, чтобы не перезаписать атрибут определением функции: def name...; name = property(name); def namesetter...; name = name.setter(namesetter)
. Но тогда было бы разумнее просто определить геттер и сеттер и просто вызвать property
один раз, а не придерживаться формы: def namegetter...; def namesetter...; name = property(namegetter, namesetter)
.
Синтаксический сахар - это, по сути, способ написать что-то более удобным для программиста способом, но не добавляющий выразительности языку; если бы он был удален из языка, вы все равно могли бы реализовать ту же функциональность. Будут ли переменные иметь одинаковые имена или нет, это не сделает язык слабее. Иногда трансформация бывает еще более радикальной: JavaScript async
/await
— это синтаксический сахар для промисов, которые, в свою очередь, являются сахаром для обратных вызовов; но преобразование кода на основе async
/await
в код на основе обещаний или обратных вызовов потребует полной реструктуризации.
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
».
Вы можете использовать модуль 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)
Это делается внутри компилятора, поэтому он не обязательно должен соответствовать реальному коду Python. Просто нужно, чтобы был такой же результат.