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 4

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.

обновление . здесь несколько более подробное объяснение того же самого.