Обратимая версия compile () в Python - PullRequest
3 голосов
/ 05 апреля 2009

Я пытаюсь сделать функцию в Python, которая делает эквивалент compile (), но также позволяет мне вернуть исходную строку. Давайте назовем эти две функции comp () и decomp () для устранения неоднозначности. То есть

a = comp("2 * (3 + x)", "", "eval")
eval(a, dict(x=3)) # => 12
decomp(a) # => "2 * (3 + x)"

Возвращаемая строка не обязательно должна быть одинаковой («2 * (3 + x)» будет приемлемым), но в принципе она должна быть такой же («2 * x + 6» не быть).

Вот что я пробовал, не работает:

  • Установка атрибута объекта кода, возвращаемого компиляцией. Вы не можете устанавливать пользовательские атрибуты для объектов кода.
  • Код подклассов, чтобы я мог добавить атрибут. код не может быть разделен на подклассы.
  • Настройка объектов кода сопоставления WeakKeyDictionary с исходными строками. на объекты кода нельзя ссылаться слабо.

Вот что работает, с проблемами:

  • Передача исходной строки кода для имени файла для компиляции (). Однако я теряю возможность фактически сохранять там имя файла, что я бы тоже хотел сделать.
  • Сохранение реального словаря, отображающего объекты кода в строки. Это приводит к утечке памяти, хотя, поскольку компиляция редка, она подходит для моего текущего варианта использования Возможно, я мог бы периодически запускать ключи через gc.get_referrers и убивать мертвые, если бы мне было нужно.

Ответы [ 2 ]

6 голосов
/ 06 апреля 2009

Это какая-то странная проблема, и моя первоначальная реакция заключается в том, что вам может быть лучше сделать что-то еще полностью, чтобы выполнить то, что вы пытаетесь сделать. Но это по-прежнему интересный вопрос, так что вот моя проблема: я делаю исходный код источника неиспользуемой константой объекта кода.

import types

def comp(source, *args, **kwargs):
    """Compile the source string; takes the same arguments as builtin compile().
    Modifies the resulting code object so that the original source can be
    recovered with decomp()."""
    c = compile(source, *args, **kwargs)
    return types.CodeType(c.co_argcount, c.co_nlocals, c.co_stacksize, 
        c.co_flags, c.co_code, c.co_consts + (source,), c.co_names, 
        c.co_varnames, c.co_filename, c.co_name, c.co_firstlineno, 
        c.co_lnotab, c.co_freevars, c.co_cellvars)

def decomp(code_object):
    return code_object.co_consts[-1]

>>> a = comp('2 * (3 + x)', '', 'eval')
>>> eval(a, dict(x=3))
12
>>> decomp(a)
'2 * (3 + x)'
4 голосов
/ 05 апреля 2009

Мой подход заключается в том, чтобы обернуть объект кода в другой объект. Примерно так:

class CodeObjectEnhanced(object):
    def __init__(self, *args):
        self.compiled = compile(*args)
        self.original = args[0]
def comp(*args):
    return CodeObjectEnhanced(*args)

Тогда всякий раз, когда вам нужен сам объект кода, вы используете a.compiled, а всякий раз, когда вам нужен оригинал, вы используете a.original. Может быть способ заставить eval обработать новый класс, как если бы он был обычным объектом кода, перенаправив функцию на вызов eval (self.compiled).

Одним из преимуществ этого является то, что исходная строка удаляется одновременно с объектом кода. Однако, как вы делаете это, я думаю, что сохранение исходной строки, вероятно, является лучшим подходом, поскольку в итоге вы получите именно ту строку, которую вы использовали, а не только приблизительное значение.

...