Вот класс 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 - лучшее, что я мог сделать.