Как использовать регулярные выражения, чтобы найти функции, которые сами вызывают?

1

У меня есть code.py:

def funA():
    print('A')
    funA()

def funB():
    print('B')

def funC():
    print('C')
    funB()
    funC()

Я хочу, чтобы все функции называли себя:

funA
funC

Как написать regex?

Ограничение:

  • Все вызовы функций нормальны: funname(arg1, arg2,...)
  • Никакие запутанные способы (такие как lambda, exec)
  • Нет косвенной рекурсии
  • 4
    Почему вы пишете регулярное выражение вместо того, чтобы ходить по AST?
  • 2
    Будет невозможно найти все рекурсивные функции с помощью регулярного выражения, поскольку грамматика Python не зависит от контекста.
Показать ещё 2 комментария
Теги:
awk

5 ответов

1
Лучший ответ

Да, я верю, что регулярное выражение было бы невозможным для соответствия случаям, указанным wim, когда сам вызов обфускается. Тем не менее, вот регулярное выражение, которое сделает funcname(...) работу для простых самостоятельных вызовов (в форме funcname(...)). Это регулярное выражение правильно соответствует всем тестовым случаям, изложенным в исходном вопросе:

reobj = re.compile(r"""
    # Match (unreliably) Python function with self reference.
    ^                        # Anchor to start of line.
    ([ \t]*)                 # $1: Indentation of DEF statement.
    def[ \t]+                # Function definition.
    ([^\s(]+)                # $2: Name of function to find.
    .*\r?\n                  # Remainder of function def line.
    (?:                      # Zero or more lines w/o funcname.
      (?:                    # Function block lines alternatives.
        \1[ \t]+             # Func block lines have extra indentation.
        (?:(?!\b\2\s*\().)*  # Optional non-funcname stuff on line
      | [ \t]*\#.*           # Allow comment lines to defy indent rules.
      )?                     # Allow blank lines in function block.
      \r?\n                  # End of line not containing funcname.
    )*                       # Zero or more lines w/o funcname
    \1[ \t]+                 # Now match the line having funcname.
    (?:(?!\b\2\s*\().)*      # Optional non-funcname stuff on line
    \b\2\s*\(                # Match the function self reference.
    """, re.MULTILINE | re.VERBOSE)

Он соответствует началу строки определения функции и фиксирует отступ пробелов перед 'def' в группе $1 и имя функции в группе $2. Затем он сопоставляет строки внутри функционального блока, которые не содержат имени функции, у каждого из которых есть более старшие пробелы, чем определение функции. Он пропускает пустые строки и строки, содержащие только комментарии. Он объявляет совпадение после того, как находит строку в функциональном блоке, который имеет имя функции, за которым следуют левые круглые скобки, указывая на вызов для себя. В противном случае он объявляет несоответствие, а затем продолжает поиск следующего возможного совпадения.

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

  • 0
    Это все равно, что дать парню травосборник, чтобы подстричь его ноздри.
  • 0
    Не совсем. Этот ответ просто отвечает на конкретный вопрос, который был задан, в комплекте с заявлением об ограничениях (очень реальных). Для одноразового поиска в исходном файле этот ответ, безусловно, может быть полезен. Просто любопытно, будет ли модуль ast работать лучше при поиске указанных вами особых случаев? Насколько трудным будет это решение? Почему бы не опубликовать это? IMO единственное 100% точное решение было бы тщательно профилировать / отлаживать приложение, выполняющее все возможные пути. Но да, это решение определенно является сорняком! (о чем просили.)
2

Это сложно, потому что функция может называть себя запутанными способами. Например, это так?

def funA():
  print 'A'
  foo = funA
  foo()

funA()

Как насчет этого?

def funA():
  funB()

def funB():
  funA()

funA()

Или даже это?

def funA():
  exec('Anuf'[::-1] + '()')

funA()

Я не думаю, что вы можете сделать это с помощью регулярного выражения.


Даже учитывая ваши новые изменения, все равно будет очень сложно, если не невозможно. Рассмотрим эту функцию, например.

def funA():
  if 1 + 1 == 2:
    return
  funA()

Я предлагаю вам следовать совету Игнасио Васкес-Абрамса и посмотреть на аш.

  • 1
    На самом деле это факт, что вы не можете сделать это с помощью регулярного выражения.
  • 0
    Я тоже так подозревал, но мне не нравится "доказательство утверждением" :)
Показать ещё 5 комментариев
1

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

0

может быть, вы можете использовать gawk, ниже мой пример кода, вам может потребоваться изменить его:

#! /usr/bin/gawk -f
{
    currentLine = $0
    if (currentLine ~ /def/){
        inFunction = "true"
        nameIndex = index($2,"(")
        functionName = substr($2,1,nameIndex - 1)
        #print functionName
        next
    }
    if (inFunction == "true" && currentLine ~ functionName){
       inFunction = false 
       print "recursive function is: " functionName
    }
}

просто запустите программу, вы получите то, что хотите.

0

Я опишу шаблон, который, как я думаю, вам нужен: строка, начинающаяся с def, за которой следует пробел, за которым следует имя (которое вы записываете в круглых скобках), за которым следует (возможно, пустой) набор строк, начинающийся с пробела, а затем строка, начинающаяся с пробела и содержащая ваше имя функции, за которым следует открытый палец (так что вы фиксируете фактический вызов, а не только ссылку).

  • 2
    Не пустой - в Python вы должны использовать инструкцию pass вместо пустого тела.
  • 0
    @PlatinumAzure def funA(): funA() - это допустимая функция Python, которая вызывает сама себя. Если сразу за ним следует другой оператор глобальной области, не будет строк, начинающихся с пробела после определения def . Однако описание Скоттом регулярного выражения также не соответствует этому.
Показать ещё 1 комментарий

Ещё вопросы

Сообщество Overcoder
Наверх
Меню