Извлечь все переменные из строки кода Python (регулярное выражение или AST) - PullRequest
1 голос
/ 04 октября 2019

Я хочу найти и извлечь все переменные в строке, содержащей код Python. Я хочу извлечь только переменные (и переменные с индексами), но не вызовы функций.

Например, из следующей строки:

code = 'foo + bar[1] + baz[1:10:var1[2+1]] + qux[[1,2,int(var2)]] + bob[len("foobar")] + func() + func2 (var3[0])'

Я хочу извлечь: foo,bar[1], baz[1:10:var1[2+1]], var1[2+1], qux[[1,2,int(var2)]], var2, bob[len("foobar")], var3[0]. Обратите внимание, что некоторые переменные могут быть «вложенными». Например, из baz[1:10:var1[2+1]] я хочу извлечь baz[1:10:var1[2+1]] и var1[2+1].

Первые две идеи, которые приходят на ум, - это использовать регулярное выражение или AST. Я пробовал оба, но безуспешно.

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

Это то, что я имею до сих пор:

regex = r'[_a-zA-Z]\w*\s*(\[.*\])?'
for match in re.finditer(regex, code):
    print(match)

Вот демонстрационная версия: https://regex101.com/r/INPRdN/2

Другое решениеиспользовать AST, расширить ast.NodeVisitor и реализовать методы visit_Name и visit_Subscript. Однако это тоже не работает, потому что для функций также вызывается visit_Name.

Буду признателен, если кто-нибудь сможет предоставить мне решение этой проблемы (регулярное выражение или AST).

Спасибо.

Ответы [ 2 ]

1 голос
/ 04 октября 2019

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

# -*- coding: utf-8 -*-
import re
RE_IDENTIFIER = r'\b[a-z]\w*\b(?!\s*[\[\("\'])'
RE_INDEX_ONLY = re.compile(r'(##)(\d+)(##)')
RE_INDEX = re.compile('##\d+##')


def extract_expression(string):
    """ extract all identifier and getitem expression in the given order."""

    def remove_brackets(text):
        # 1. handle `[...]` expression replace them with #{#...#}#
        # so we don't confuse them with word[...]
        pattern = '(?<!\w)(\s*)(\[)([^\[]+?)(\])'
        # keep extracting expression until there is no expression
        while re.search(pattern, text):
            text = re.sub(pattern, r'\1#{#\3#}#', string)
        return text

    def get_ordered_subexp(exp):
        """ get index of nested expression."""
        index = int(exp.replace('#', ''))
        subexp = RE_INDEX.findall(expressions[index])
        if not subexp:
            return exp
        return exp + ''.join(get_ordered_subexp(i) for i in subexp)

    def replace_expression(match):
        """ save the expression in the list, replace it with special key and it's index in the list."""
        match_exp = match.group(0)
        current_index = len(expressions)
        expressions.append(None)  # just to make sure the expression is inserted before it's inner identifier
        # if the expression contains identifier extract too.
        if re.search(RE_IDENTIFIER, match_exp) and '[' in match_exp:
            match_exp = re.sub(RE_IDENTIFIER, replace_expression, match_exp)
        expressions[current_index] = match_exp
        return '##{}##'.format(current_index)

    def fix_expression(match):
        """ replace the match by the corresponding expression using the index"""
        return expressions[int(match.group(2))]

    # result that will contains
    expressions = []

    string = remove_brackets(string)

    # 2. extract all expression and keep track of there place in the original code
    pattern = r'\w+\s*\[[^\[]+?\]|{}'.format(RE_IDENTIFIER)
    # keep extracting expression until there is no expression
    while re.search(pattern, string):
        # every exression that is extracted is replaced by a special key
        string = re.sub(pattern, replace_expression, string)
        # some times inside brackets can contains getitem expression
        # so when we extract that expression we handle the brackets
        string = remove_brackets(string)

    # 3. build the correct result with extracted expressions
    result = [None] * len(expressions)
    for index, exp in enumerate(expressions):
        # keep replacing special keys with the correct expression
        while RE_INDEX_ONLY.search(exp):
            exp = RE_INDEX_ONLY.sub(fix_expression, exp)
        # finally we don't forget about the brackets
        result[index] = exp.replace('#{#', '[').replace('#}#', ']')

    # 4. Order the index that where extracted
    ordered_index = ''.join(get_ordered_subexp(exp) for exp in RE_INDEX.findall(string))
    # convert it to integer
    ordered_index = [int(index[1]) for index in RE_INDEX_ONLY.findall(ordered_index)]

    # 5. fix the order of expressions using the ordered indexes
    final_result = []
    for exp_index in ordered_index:
        final_result.append(result[exp_index])

    # for debug:
    # print('final string:', string)
    # print('expression :', expressions)
    # print('order_of_expresion: ', ordered_index)
    return final_result


code = 'foo + bar[1] + baz[1:10:var1[2+1]] + qux[[1,2,int(var2)]] + bob[len("foobar")] + func() + func2 (var3[0])'
code2 = 'baz[1:10:var1[2+1]]'
code3 = 'baz[[1]:10:var1[2+1]:[var3[3+1*x]]]'
print(extract_expression(code))
print(extract_expression(code2))
print(extract_expression(code3))

OUTPU:

['foo', 'bar[1]', 'baz[1:10:var1[2+1]]', 'var1[2+1]', 'qux[[1,2,int(var2)]]', 'var2', 'bob[len("foobar")]', 'var3[0]']
['baz[1:10:var1[2+1]]', 'var1[2+1]']
['baz[[1]:10:var1[2+1]:[var3[3+1*x]]]', 'var1[2+1]', 'var3[3+1*x]', 'x']

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

0 голосов
/ 04 октября 2019

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

Этот вопрос часто задают, и связанный ответ известен тем, что демонстрирует сложность того, что вы пытаетесь сделать

Если вам действительно нужно проанализировать строку длякод AST будет работать технически, но я не знаю, какая библиотека поможет с этим. Лучше всего попытаться создать рекурсивную функцию для анализа.

...