Как вы переводите эту идиому регулярного выражения из Perl в Python? - PullRequest
45 голосов
/ 23 сентября 2008

Я перешел с Perl на Python около года назад и не оглядывался назад. Есть только одна идиома, которую я когда-либо обнаружил, я могу сделать это легче в Perl, чем в Python:

if ($var =~ /foo(.+)/) {
  # do something with $1
} elsif ($var =~ /bar(.+)/) {
  # do something with $1
} elsif ($var =~ /baz(.+)/) {
  # do something with $1
}

Соответствующий код Python не так элегантен, поскольку операторы if продолжают вкладываться:

m = re.search(r'foo(.+)', var)
if m:
  # do something with m.group(1)
else:
  m = re.search(r'bar(.+)', var)
  if m:
    # do something with m.group(1)
  else:
    m = re.search(r'baz(.+)', var)
    if m:
      # do something with m.group(2)

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

Ответы [ 14 ]

1 голос
/ 09 августа 2016

Немного расширив решение Пат Нотц, я нашел его еще более элегантным:
- назовите методы так же, как re (например, search() против check()) и
- реализовать необходимые методы, такие как group(), на самом объекте-держателе:

class Re(object):
    def __init__(self):
        self.result = None

    def search(self, pattern, text):
        self.result = re.search(pattern, text)
        return self.result

    def group(self, index):
        return self.result.group(index)

Пример

Вместо, например, это:

m = re.search(r'set ([^ ]+) to ([^ ]+)', line)
if m:
    vars[m.group(1)] = m.group(2)
else:
    m = re.search(r'print ([^ ]+)', line)
    if m:
        print(vars[m.group(1)])
    else:
        m = re.search(r'add ([^ ]+) to ([^ ]+)', line)
        if m:
            vars[m.group(2)] += vars[m.group(1)]

Один делает только это:

m = Re()
...
if m.search(r'set ([^ ]+) to ([^ ]+)', line):
    vars[m.group(1)] = m.group(2)
elif m.search(r'print ([^ ]+)', line):
    print(vars[m.group(1)])
elif m.search(r'add ([^ ]+) to ([^ ]+)', line):
    vars[m.group(2)] += vars[m.group(1)]

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

1 голос
/ 16 ноября 2010

как насчет использования словаря?

match_objects = {}

if match_objects.setdefault( 'mo_foo', re_foo.search( text ) ):
  # do something with match_objects[ 'mo_foo' ]

elif match_objects.setdefault( 'mo_bar', re_bar.search( text ) ):
  # do something with match_objects[ 'mo_bar' ]

elif match_objects.setdefault( 'mo_baz', re_baz.search( text ) ):
  # do something with match_objects[ 'mo_baz' ]

...

однако вы должны убедиться, что в словаре совпадений нет повторяющихся ключей match_objects (mo_foo, mo_bar, ...). Лучше всего, если каждому регулярному выражению присваивать собственное имя и соответственно присваивать имена ключам match_objects, в противном случае метод match_objects.setdefault () вернётся. существующий объект соответствия вместо создания нового объекта соответствия путем запуска re_xxx.search (text).

0 голосов
/ 21 августа 2017

Вот класс RegexDispatcher, который отправляет свои методы подкласса с помощью регулярного выражения.

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

def plus(self, regex: r"\+", **kwargs):
...

В этом случае аннотация называется 'regex', а ее значением является регулярное выражение для сопоставления '\ +', которое является знаком +. Эти аннотированные методы помещаются в подклассы, а не в базовый класс.

Когда метод dispatch (...) вызывается для строки, класс находит метод с регулярным выражением аннотации, который соответствует строке, и вызывает ее. Вот класс:

import inspect
import re


class RegexMethod:
    def __init__(self, method, annotation):
        self.method = method
        self.name = self.method.__name__
        self.order = inspect.getsourcelines(self.method)[1] # The line in the source file
        self.regex = self.method.__annotations__[annotation]

    def match(self, s):
        return re.match(self.regex, s)

    # Make it callable
    def __call__(self, *args, **kwargs):
        return self.method(*args, **kwargs)

    def __str__(self):
        return str.format("Line: %s, method name: %s, regex: %s" % (self.order, self.name, self.regex))


class RegexDispatcher:
    def __init__(self, annotation="regex"):
        self.annotation = annotation
        # Collect all the methods that have an annotation that matches self.annotation
        # For example, methods that have the annotation "regex", which is the default
        self.dispatchMethods = [RegexMethod(m[1], self.annotation) for m in
                                inspect.getmembers(self, predicate=inspect.ismethod) if
                                (self.annotation in m[1].__annotations__)]
        # Be sure to process the dispatch methods in the order they appear in the class!
        # This is because the order in which you test regexes is important.
        # The most specific patterns must always be tested BEFORE more general ones
        # otherwise they will never match.
        self.dispatchMethods.sort(key=lambda m: m.order)

    # Finds the FIRST match of s against a RegexMethod in dispatchMethods, calls the RegexMethod and returns
    def dispatch(self, s, **kwargs):
        for m in self.dispatchMethods:
            if m.match(s):
                return m(self.annotation, **kwargs)
        return None

Чтобы использовать этот класс, создайте подкласс для создания класса с аннотированными методами. В качестве примера, вот простой RPNCalculator, который наследуется от RegexDispatcher. Методы, которые должны быть отправлены, - это, конечно, методы с аннотацией 'regex'. Родительский метод dispatch () вызывается в call .

from RegexDispatcher import *
import math

class RPNCalculator(RegexDispatcher):
    def __init__(self):
        RegexDispatcher.__init__(self)
        self.stack = []

    def __str__(self):
        return str(self.stack)

    # Make RPNCalculator objects callable
    def __call__(self, expression):
        # Calculate the value of expression
        for t in expression.split():
            self.dispatch(t, token=t)
        return self.top()  # return the top of the stack

    # Stack management
    def top(self):
        return self.stack[-1] if len(self.stack) > 0 else []

    def push(self, x):
        return self.stack.append(float(x))

    def pop(self, n=1):
        return self.stack.pop() if n == 1 else [self.stack.pop() for n in range(n)]

    # Handle numbers
    def number(self, regex: r"[-+]?[0-9]*\.?[0-9]+(?:[eE][-+]?[0-9]+)?", **kwargs):
        self.stack.append(float(kwargs['token']))

    # Binary operators
    def plus(self, regex: r"\+", **kwargs):
        a, b = self.pop(2)
        self.push(b + a)

    def minus(self, regex: r"\-", **kwargs):
        a, b = self.pop(2)
        self.push(b - a)

    def multiply(self, regex: r"\*", **kwargs):
        a, b = self.pop(2)
        self.push(b * a)

    def divide(self, regex: r"\/", **kwargs):
        a, b = self.pop(2)
        self.push(b / a)

    def pow(self, regex: r"exp", **kwargs):
        a, b = self.pop(2)
        self.push(a ** b)

    def logN(self, regex: r"logN", **kwargs):
        a, b = self.pop(2)
        self.push(math.log(a,b))

    # Unary operators
    def neg(self, regex: r"neg", **kwargs):
        self.push(-self.pop())

    def sqrt(self, regex: r"sqrt", **kwargs):
        self.push(math.sqrt(self.pop()))

    def log2(self, regex: r"log2", **kwargs):
        self.push(math.log2(self.pop()))

    def log10(self, regex: r"log10", **kwargs):
        self.push(math.log10(self.pop()))

    def pi(self, regex: r"pi", **kwargs):
        self.push(math.pi)

    def e(self, regex: r"e", **kwargs):
        self.push(math.e)

    def deg(self, regex: r"deg", **kwargs):
        self.push(math.degrees(self.pop()))

    def rad(self, regex: r"rad", **kwargs):
        self.push(math.radians(self.pop()))

    # Whole stack operators
    def cls(self, regex: r"c", **kwargs):
        self.stack=[]

    def sum(self, regex: r"sum", **kwargs):
        self.stack=[math.fsum(self.stack)]


if __name__ == '__main__':
    calc = RPNCalculator()

    print(calc('2 2 exp 3 + neg'))

    print(calc('c 1 2 3 4 5 sum 2 * 2 / pi'))

    print(calc('pi 2 * deg'))

    print(calc('2 2 logN'))

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

Для интереса взгляните на язык Wolfram, в котором функции полиморфны для произвольных шаблонов, а не только для типов аргументов. Функция, которая является полиморфной в регулярном выражении, является очень мощной идеей, но мы не можем сделать это чисто в Python. Класс RegexDispatcher - лучшее, что я мог сделать.

0 голосов
/ 12 июня 2015

Мое решение будет:

import re

class Found(Exception): pass

try:        
    for m in re.finditer('bar(.+)', var):
        # Do something
        raise Found

    for m in re.finditer('foo(.+)', var):
        # Do something else
        raise Found

except Found: pass
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...