Встроенная функция Python - PullRequest
0 голосов
/ 02 мая 2018

В программе на C вставка функции является довольно интуитивной оптимизацией. Если тело встроенной функции достаточно мало, вы в конечном итоге сохраняете переход к функции и создаете кадр стека и сохраняете возвращаемое значение везде, где был бы сохранен результат функции, переходя в конец тела встроенной функции. "вместо длинного перехода к указателю возврата.

Я заинтересован в том, чтобы сделать то же самое в Python, преобразовав две функции python в другую допустимую функцию python, где первая «встроена» во вторую. Идеальное решение этого может выглядеть примерно так:

def g(x):
    return x ** 2

def f(y):
    return g(y + 3)

# ... Becomes ...

def inlined_f(y):
    return (y + 3) ** 2

Очевидно, что на языке, таком динамическом, как Python, это не просто сделать автоматически. Лучшее общее решение, которое я придумала, это использовать dict для захвата аргументов, переданных функции, обернуть тело функции в одноповторный цикл for, использовать break для перехода к концу функции и замените использование аргументов индексами в словарь аргументов. Результат выглядит примерно так:

def inlined_f(y):
    _g = dict(x=y + 3)
    for ____ in [None]:
        _g['return'] = _g['x'] ** 2
        break
    _g_return = _g.get('return', None)
    del _g
    return _g_return

Мне все равно, что это некрасиво, но мне важно, чтобы оно не поддерживало возвраты из циклов. E.g.:

def g(x):
    for i in range(x + 1):
        if i == x:
            return i ** 2
    print("Woops, you shouldn't get here")

def inlined_f(y):
    _g = dict(x=y + 3)
    for ____ in [None]:
        for _g['i'] in range(_g['x'] + 1):
            if _g['i'] == _g['x']:
                _g['return'] _g['i'] ** 2
                break  # <-- Doesn't exit function, just innermost loop
        print("Woops, you shouldn't get here")
    _g_return = _g.get('return', None)
    del _g
    return _g_return

Какой подход я мог бы применить к этой проблеме, чтобы избежать необходимости использовать break, чтобы «выпрыгнуть» из тела встроенной функции? Я также был бы открыт для общего лучшего общего подхода, который я мог бы использовать, чтобы встроить одну функцию Python в другую.

Для справки, я работаю на уровне AST (абстрактного синтаксического дерева), поэтому использую анализируемый код Python; ясно, что вне литеральных значений я не знаю, какое значение или тип что-либо будет иметь при выполнении этого преобразования. Результирующая встроенная функция должна вести себя идентично исходным функциям и поддерживать все функции, обычно доступные при вызове функции. Возможно ли это даже в Python?


РЕДАКТИРОВАТЬ: я должен уточнить, так как я использовал тег «оптимизация», что я на самом деле не заинтересован в повышении производительности. Результирующий код не обязательно должен быть быстрее , он просто не должен вызывать встроенную функцию, все еще ведя себя идентично. Вы можете предположить, что исходный код обеих функций доступен как допустимый Python.

Ответы [ 2 ]

0 голосов
/ 02 мая 2018

Единственный разумный способ на уровне источника Понятно, упрощенно:

  • Разобрать источник в некоторый AST (или просто использовать встроенный AST ).
  • Скопировать поддерево, представляющее тело функции.
  • Переименуйте переменные в поддереве, например, добавив уникальный префикс.
  • На сайте вызова замените все переданные аргументы присваиваниями, используя новые имена переменных функции.
  • Удалите вызов и замените его на тело функции, которое вы подготовили.
  • Сериализация AST обратно к источнику.

Что создает реальные проблемы:

  • Функции генератора; просто не вставляйте их.
  • Возвращает из-под try / finally, которые должны запустить часть finally. Может быть довольно сложно переписать правильно; imho, лучше оставить без подкладки.
  • Возвращает из-под менеджеров контекста, которым нужно запустить части __exit__. Хотя это и не невозможно, но также сложно переписать с сохранением семантики; Скорее всего, лучше не вводить.
  • Mid-функция возвращает, особенно из нескольких конструкций цикла. Возможно, вам придется заменить их дополнительной переменной и включить их в каждое условие каждого оператора while и, вероятно, добавить условный разрыв к операторам for. Опять же, не невозможно, но, скорее всего, лучше оставить его без строки.
0 голосов
/ 02 мая 2018

Вероятно, ближайшим аналогом return будет повышение Exception, которое будет работать, чтобы выскочить из вложенных циклов на вершину "встроенной функции".

class ReturnException(Exception):
    pass


g = dict(x=y + 3)
try:
    for j in some_loop:
        for _g['i'] in range(_g['x'] + 1):
            if _g['i'] == _g['x']:
                raise ReturnException(_g['i'] ** 2)
except ReturnException as e:
    _g['return'] = e.message
else:
    _g['return'] = None

Я не знаю, какие накладные расходы связаны с исключениями, или если это будет быстрее, чем простой вызов функции.

...