Макросы делают преобразования кода
Макрос преобразует исходный код. Леневой оценки нет. Представьте, что теперь вы можете писать функции, которые преобразуют произвольный код в произвольный код.
Очень простые преобразования кода
Создание простых языковых конструкций также является очень простым примером. Рассмотрим ваш пример открытия файла:
(with-open-file (stream file :direction :input)
(do-something stream))
vs.
(call-with-stream (function do-something)
file
:direction :input)
Что дает мне макрос, это немного другой синтаксис и структура кода.
Встроенный язык: расширенные конструкции итераций
Далее рассмотрим несколько другой пример:
(loop for i from 10 below 20 collect (sqr i))
vs.
(collect-for 10 20 (function sqr))
Мы можем определить функцию COLLECT-FOR
, который делает то же самое для простого цикла и имеет переменные для начала, конца и функции шага.
Но LOOP
предоставляет новый язык. Макрос LOOP
является компилятором для этого языка. Этот компилятор может выполнить LOOP
конкретные оптимизации и также может проверить синтаксис во время компиляции для этого нового языка. Еще более мощный макрос цикла ITERATE
. Эти мощные инструменты на уровне языка теперь могут быть написаны как библиотеки без какой-либо специальной поддержки компилятора.
Прогулка по дереву кода в макросе и внесение изменений
Следующий еще один простой пример:
(with-slots (age name) some-person
(print name)
(princ " "
(princ age))
vs. что-то похожее:
(flet ((age (person) (slot-value person 'age))
(name (person) (slot-value person 'name)))
(print (name))
(princ " ")
(princ (age)))
Макрос WITH-SLOTS
вызывает полное перемещение вложенного исходного дерева и заменяет имя переменной вызовом на (SLOT-VALUE SOME-PERSON 'name)
:
(progn
(print (slot-value some-person 'name))
(princ " "
(princ (slot-value some-person 'age)))
В этом случае макрос может переписать отдельные части кода. Он понимает структуру языка Lisp и знает, что имя и возраст являются переменными. Он также понимает, что в некоторых ситуациях name
и age
могут не быть переменными и не должны быть переписаны. Это приложение так называемого кода Walker , инструмент, который может перемещаться по кодам деревьев и вносить изменения в дерево кода.
Макросы могут изменять среду компиляции
Еще один простой пример: содержимое небольшого файла:
(defmacro oneplus (x)
(print (list 'expanding 'oneplus 'with x))
`(1+ ,x))
(defun example (a b)
(+ (oneplus a) (oneplus (* a b))))
В этом примере нас не интересует макрос ONEPLUS
, а сам макрос DEFMACRO
.
Что интересно? В Lisp вы можете иметь файл с указанным выше содержимым и использовать компилятор file для компиляции этого файла.
;;; Compiling file /private/tmp/test.lisp ...
;;; Safety = 3, Speed = 1, Space = 1, Float = 1, Interruptible = 1
;;; Compilation speed = 1, Debug = 2, Fixnum safety = 3
;;; Source level debugging is on
;;; Source file recording is on
;;; Cross referencing is on
; (TOP-LEVEL-FORM 0)
; ONEPLUS
(EXPANDING ONEPLUS SOURCE A)
(EXPANDING ONEPLUS SOURCE (* A B))
; EXAMPLE
;; Processing Cross Reference Information
Итак, мы видим, что компилятор file g2] расширяет использование макроса ONEPLUS
.
Что особенного в этом? В файле есть определение макроса, и в следующем виде мы уже используем этот новый макрос ONEPLUS
. Мы никогда не загружали определение макроса в Lisp. Как-то компилятор знает и регистрирует определенный макрос ONEPLUS
и затем может его использовать.
Таким образом, макрос DEFMACRO
регистрирует вновь определенный макрос ONEPLUS
во время компиляции, так что компилятор знает об этом макросе - без загрузки кода. Затем макрос можно выполнить во время компиляции во время расширения макроса.
С помощью функции мы не можем этого сделать. Компилятор создает код для вызовов функций, но не запускает их. Но макрос можно запустить во время компиляции и добавить «знание» в компилятор. Это знание тогда действует во время запуска компилятора и частично забывается позже. DEFMACRO
- макрос, который выполняется во время компиляции, а затем информирует среду компиляции нового макроса.
Обратите внимание, что макрос ONEPLUS
также запускается дважды, поскольку он используется дважды в файл. Побочным эффектом является то, что он что-то печатает. Но ONEPLUS
может иметь и другие произвольные побочные эффекты. Например, он может проверить закрытый источник на базе правил и предупредить вас, если, например, закрытый код нарушает некоторые правила (подумайте о проверке стиля).
Это означает, что макрос здесь DEFMACRO
, может изменять язык и среду при компиляции файла. В других языках компилятор может предоставить специальные директивы компилятора, которые будут распознаны во время компиляции. Существует множество примеров таких макросов, которые влияют на компилятор: DEFUN
, DEFCLASS
, DEFMETHOD
, ...
Макросы могут сделать код пользователя короче
Например, макрос DEFSTRUCT
для определения структур данных записи .
(defstruct person name age salary)
Выше defstruct
макрос создает код для
person
с тремя слотами person
make-person
функция для создания структурных объектов Кроме того, он может:
Исходный код для определения структуры - это короткая строка. Расширенный код намного длиннее.
Макрос DEFSTRUCT
не нуждается в доступе к метауровню языка для создания этих различных вещей. Он просто преобразует компактный фрагмент описательного кода в, как правило, более длинный, определяющий код, используя типичные языковые конструкции.
Заключение в кавычки Jacob Hallen :
надлежащее использование
__slots__
должно оставить свободное место в объектах. Вместо того, чтобы иметь динамический dict, который позволяет добавлять атрибуты к объектам в любое время, существует статическая структура, которая не позволяет дополнения после создания. [Это использование__slots__
устраняет издержки одного dict для каждого объекта.], В то время как это иногда - полезная оптимизация, было бы абсолютно ненужным, если бы интерпретатор Python был достаточно динамичным так, чтобы это только потребовало бы dict, когда на самом деле были дополнения к объекту., К сожалению, существует побочный эффект к слотам. Они изменяют поведение объектов, которые имеют слоты способом, которыми могут злоупотребить препоны статического контроля типов и любители командовать. Это плохо, потому что любители командовать должны злоупотреблять метаклассами, и препоны статического контроля типов должны оскорблять декораторов, с тех пор в Python, должен быть только один очевидный способ сделать что-то.
Создание CPython, достаточно умный для обработки оставляющего свободного места без
__slots__
, является основным обязательством, которое является, вероятно, почему это (еще) не находится в списке изменений для P3k.
Каждый объект Python имеет __dict__
атрибут, который является словарем, содержащим все другие атрибуты. например, когда Вы Python типа self.attr
на самом деле делаете self.__dict__['attr']
. Поскольку можно предположить использовать словарь для хранения атрибута, занимает некоторое дополнительное место & время для доступа к нему.
Однако, когда Вы будете использовать __slots__
, любой объект, созданный для того класса, не будет иметь __dict__
атрибут. Вместо этого весь доступ атрибута сделан непосредственно через указатели.
Поэтому, если хотят структуру стиля C, а не абсолютный класс, можно использовать __slots__
для уплотнения размера объектов & сокращение времени доступа атрибута. Хорошим примером является класс Точки, содержащий атрибуты x & y. Если Вы собираетесь иметь много точек, можно попытаться использовать __slots__
для сохранения некоторой памяти.
Вы хотели бы использовать __slots__
, если Вы собираетесь инстанцировать много (сотни, тысячи) объектов того же класса. __slots__
только существует как инструмент оптимизации памяти.
Этому высоко препятствуют для использования __slots__
для ограничения создания атрибута, и в целом Вы хотите избежать его, потому что это повреждает рассол, наряду с некоторыми другими функциями самоанализа Python.
У Вас есть — по существу — бывший бесполезный для __slots__
.
В течение времени, когда Вы думаете, что Вам, возможно, понадобилось бы __slots__
, Вы на самом деле хотите использовать Легкий вес или Боксер наилегчайшего веса шаблоны разработки. Это случаи, когда Вы больше не хотите использовать просто объекты Python. Вместо этого Вы хотите Python подобная объекту обертка вокруг массива, структуры или массива numpy.
class Flyweight(object):
def get(self, theData, index):
return theData[index]
def set(self, theData, index, value):
theData[index]= value
подобная классу обертка не имеет никаких атрибутов —, она просто предоставляет методы, которые действуют на базовые данные. Методы могут быть уменьшены до методов класса. Действительно, это могло быть уменьшено до просто функций, воздействующих на основной массив данных.