Python setattr и getattr для глобальной области видимости?
Предположим, мне нужно создать свой собственный небольшой DSL, который будет использовать Python для описания определенной структуры данных. Например, я хотел бы иметь возможность написать что-то вроде
f(x) = some_stuff(a,b,c)
И пусть Python вместо того, чтобы жаловаться на необъявленные идентификаторы или пытаться вызвать функцию some_stuff, преобразует ее в буквальное выражение для моего дальнейшего удобства.
Можно получить разумное приближение к этому, создав класс с правильно переопределенными __getattr__
и __setattr__
методами и используйте его следующим образом:
e = Expression()
e.f[e.x] = e.some_stuff(e.a, e.b, e.c)
Было бы здорово, если бы можно было избавиться от раздражающих префиксов "е" и, возможно, даже избежать использования []. Поэтому мне было интересно, можно ли как-то временно "переопределить" глобальные поисковые запросы и назначения имен? В связи с этим, может быть, есть хорошие пакеты для легкого достижения такой функциональности "цитирования" для выражений Python?
3 ответа:
Я не уверен, что это хорошая идея, но я подумал, что должен попробовать. Подведем итог:
class PermissiveDict(dict): default = None def __getitem__(self, item): try: return dict.__getitem__(self, item) except KeyError: return self.default def exec_with_default(code, default=None): ns = PermissiveDict() ns.default = default exec code in ns return ns
Возможно, вы захотите взглянуть на модули
ast
илиparser
, входящие в состав Python, чтобы проанализировать, получить доступ и преобразовать абстрактное синтаксическое дерево (или дерево синтаксического анализа соответственно) входного кода. Насколько мне известно, математическая система Sage, написанная на Python, имеет аналогичный прекомпилятор.
В ответ на комментарий Вая, вот одно забавное решение, которое я нашел. Прежде всего, чтобы еще раз объяснить, что он делает, предположим, что у вас есть следующий код:
definitions = Structure() definitions.add_definition('f[x]', 'x*2') definitions.add_definition('f[z]', 'some_function(z)') definitions.add_definition('g.i', 'some_object[i].method(param=value)')
Где добавление определений подразумевает разбор левой и правой сторон и выполнение других уродливых вещей. Теперь один (не обязательно хороший, но определенно интересный) подход здесь позволил бы написать приведенный выше код следующим образом:
@my_dsl def definitions(): f[x] = x*2 f[z] = some_function(z) g.i = some_object[i].method(param=value)
И пусть Python сделает большую часть разбора под капотом. Идея такова основано на простом утверждении
exec <code> in <environment>
, упомянутом Яном, с одним хакским дополнением. А именно, байт-код функции должен быть слегка изменен и все операции доступа к локальной переменной (LOAD_FAST) переключены на доступ к переменной из окружения (LOAD_NAME).Это легче показать, чем объяснить: http://fouryears.eu/wp-content/uploads/pydsl/
Есть различные трюки, которые вы можете сделать, чтобы сделать его практичным. Например, в коде, представленном по ссылке выше вас нельзя использовать встроенные функции и языковые конструкции, такие как For loops и if в функции @my_dsl. Однако вы можете заставить их работать, добавив больше поведения в класс Env.обновление . здесь несколько более подробное объяснение того же самого.