regulares - ¿Cómo traduces este idioma de expresión regular de Perl a Python?
frecuencia de palabras python (13)
Cambié de Perl a Python hace aproximadamente un año y no he mirado atrás. Solo hay un modismo que he encontrado que puedo hacer más fácilmente en Perl que en Python:
if ($var =~ /foo(.+)/) {
# do something with $1
} elsif ($var =~ /bar(.+)/) {
# do something with $1
} elsif ($var =~ /baz(.+)/) {
# do something with $1
}
El código Python correspondiente no es tan elegante ya que las declaraciones if siguen anidadas:
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)
¿Alguien tiene una forma elegante de reproducir este patrón en Python? He visto utilizar tablas de despacho de funciones anónimas, pero me parecen un poco difíciles de manejar para una pequeña cantidad de expresiones regulares ...
¿Qué hay de usar un diccionario?
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'' ]
...
sin embargo, debe asegurarse de que no haya duplicados claves del diccionario match_objects (mo_foo, mo_bar, ...), lo mejor es darle a cada expresión regular su propio nombre y nombrar las claves match_objects en consecuencia, de lo contrario el método match_objects.setdefault () devolvería el objeto de coincidencia existente en lugar de crear un nuevo objeto de coincidencia ejecutando re_xxx.search (texto).
Alternativamente, algo que no usa expresiones regulares en absoluto:
prefix, data = var[:3], var[3:]
if prefix == ''foo'':
# do something with data
elif prefix == ''bar'':
# do something with data
elif prefix == ''baz'':
# do something with data
else:
# do something with var
Si eso es adecuado depende de su problema real. No olvides, las expresiones regulares no son las navajas suizas que están en Perl; Python tiene diferentes constructos para manipular cadenas.
Ampliando un poco la solución de Pat Notz, encontré incluso más elegante para:
- nombre los métodos igual que re
proporciona (por ejemplo, search()
vs. check()
) y
- Implementar los métodos necesarios como group()
en el objeto holder en sí:
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)
Ejemplo
En lugar de, por ejemplo, esto:
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)]
Uno hace exactamente esto:
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)]
Parece muy natural al final, no necesita demasiados cambios de código cuando se mueve desde Perl y evita los problemas con el estado global como algunas otras soluciones.
Aquí hay una clase RegexDispatcher que distribuye sus métodos de subclase por expresión regular.
Cada método despachable está anotado con una expresión regular, por ejemplo
def plus(self, regex: r"/+", **kwargs):
...
En este caso, la anotación se llama ''regex'' y su valor es la expresión regular para hacer coincidir, ''/ +'', que es el signo +. Estos métodos anotados se colocan en subclases, no en la clase base.
Cuando se llama al método dispatch (...) en una cadena, la clase encuentra el método con una expresión regular de anotación que coincide con la cadena y la llama. Aquí está la clase:
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
Para usar esta clase, subclasselo para crear una clase con métodos anotados. A modo de ejemplo, aquí hay un simple RPNCalculator que hereda de RegexDispatcher. Los métodos que se enviarán son (por supuesto) los que tienen la anotación ''regex''. El método parent dispatch () se invoca en la llamada .
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''))
Me gusta esta solución porque no hay tablas de búsqueda separadas. La expresión regular para coincidir está incrustada en el método que se llamará como una anotación. Para mí, esto es lo que debería ser. Sería bueno si Python permitiera anotaciones más flexibles, porque preferiría poner la anotación regex en el método en lugar de incrustarla en la lista de parámetros del método. Sin embargo, esto no es posible en este momento.
Para mayor información, eche un vistazo al lenguaje Wolfram en el cual las funciones son polimórficas en patrones arbitrarios, no solo en tipos de argumentos. Una función que es polimórfica en una expresión regular es una idea muy poderosa, pero no podemos llegar limpiamente en Python. La clase RegexDispatcher es lo mejor que pude hacer.
Así es como resolví este problema:
matched = False;
m = re.match("regex1");
if not matched and m:
#do something
matched = True;
m = re.match("regex2");
if not matched and m:
#do something else
matched = True;
m = re.match("regex3");
if not matched and m:
#do yet something else
matched = True;
No es tan limpio como el patrón original. Sin embargo, es simple, directo y no requiere módulos adicionales o que usted cambie las expresiones regulares originales.
Gracias a esta otra pregunta SO :
import re
class DataHolder:
def __init__(self, value=None, attr_name=''value''):
self._attr_name = attr_name
self.set(value)
def __call__(self, value):
return self.set(value)
def set(self, value):
setattr(self, self._attr_name, value)
return value
def get(self):
return getattr(self, self._attr_name)
string = u''test bar 123''
save_match = DataHolder(attr_name=''match'')
if save_match(re.search(''foo (/d+)'', string)):
print "Foo"
print save_match.match.group(1)
elif save_match(re.search(''bar (/d+)'', string)):
print "Bar"
print save_match.match.group(1)
elif save_match(re.search(''baz (/d+)'', string)):
print "Baz"
print save_match.match.group(1)
Mi solución sería:
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
Sí, es un poco molesto. Tal vez esto funcione para su caso.
import re
class ReCheck(object):
def __init__(self):
self.result = None
def check(self, pattern, text):
self.result = re.search(pattern, text)
return self.result
var = ''bar stuff''
m = ReCheck()
if m.check(r''foo(.+)'',var):
print m.result.group(1)
elif m.check(r''bar(.+)'',var):
print m.result.group(1)
elif m.check(r''baz(.+)'',var):
print m.result.group(1)
EDITAR: Brian señaló correctamente que mi primer intento no funcionó. Desafortunadamente, este intento es más largo.
Sugeriría esto, ya que usa la menor expresión regular para lograr tu objetivo. Todavía es un código funcional, pero no es peor que tu antiguo Perl.
import re
var = "barbazfoo"
m = re.search(r''(foo|bar|baz)(.+)'', var)
if m.group(1) == ''foo'':
print m.group(1)
# do something with m.group(1)
elif m.group(1) == "bar":
print m.group(1)
# do something with m.group(1)
elif m.group(1) == "baz":
print m.group(2)
# do something with m.group(2)
Un DataHolder minimalista:
class Holder(object):
def __call__(self, *x):
if x:
self.x = x[0]
return self.x
data = Holder()
if data(re.search(''foo (/d+)'', string)):
print data().group(1)
o como una función singleton:
def data(*x):
if x:
data.x = x[0]
return data.x
Usar grupos nombrados y una tabla de envío:
r = re.compile(r''(?P<cmd>foo|bar|baz)(?P<data>.+)'')
def do_foo(data):
...
def do_bar(data):
...
def do_baz(data):
...
dispatch = {
''foo'': do_foo,
''bar'': do_bar,
''baz'': do_baz,
}
m = r.match(var)
if m:
dispatch[m.group(''cmd'')](m.group(''data''))
Con un poco de introspección puede generar automáticamente la expresión regular y la tabla de envío.
def find_first_match(string, *regexes):
for regex, handler in regexes:
m = re.search(regex, string):
if m:
handler(m)
return
else:
raise ValueError
find_first_match(
foo,
(r''foo(.+)'', handle_foo),
(r''bar(.+)'', handle_bar),
(r''baz(.+)'', handle_baz))
Para acelerarlo, uno puede convertir todas las expresiones regulares en una internamente y crear el despachador sobre la marcha. Idealmente, esto se convertiría en una clase entonces.
r"""
This is an extension of the re module. It stores the last successful
match object and lets you access it''s methods and attributes via
this module.
This module exports the following additional functions:
expand Return the string obtained by doing backslash substitution on a
template string.
group Returns one or more subgroups of the match.
groups Return a tuple containing all the subgroups of the match.
start Return the indices of the start of the substring matched by
group.
end Return the indices of the end of the substring matched by group.
span Returns a 2-tuple of (start(), end()) of the substring matched
by group.
This module defines the following additional public attributes:
pos The value of pos which was passed to the search() or match()
method.
endpos The value of endpos which was passed to the search() or
match() method.
lastindex The integer index of the last matched capturing group.
lastgroup The name of the last matched capturing group.
re The regular expression object which as passed to search() or
match().
string The string passed to match() or search().
"""
import re as re_
from re import *
from functools import wraps
__all__ = re_.__all__ + [ "expand", "group", "groups", "start", "end", "span",
"last_match", "pos", "endpos", "lastindex", "lastgroup", "re", "string" ]
last_match = pos = endpos = lastindex = lastgroup = re = string = None
def _set_match(match=None):
global last_match, pos, endpos, lastindex, lastgroup, re, string
if match is not None:
last_match = match
pos = match.pos
endpos = match.endpos
lastindex = match.lastindex
lastgroup = match.lastgroup
re = match.re
string = match.string
return match
@wraps(re_.match)
def match(pattern, string, flags=0):
return _set_match(re_.match(pattern, string, flags))
@wraps(re_.search)
def search(pattern, string, flags=0):
return _set_match(re_.search(pattern, string, flags))
@wraps(re_.findall)
def findall(pattern, string, flags=0):
matches = re_.findall(pattern, string, flags)
if matches:
_set_match(matches[-1])
return matches
@wraps(re_.finditer)
def finditer(pattern, string, flags=0):
for match in re_.finditer(pattern, string, flags):
yield _set_match(match)
def expand(template):
if last_match is None:
raise TypeError, "No successful match yet."
return last_match.expand(template)
def group(*indices):
if last_match is None:
raise TypeError, "No successful match yet."
return last_match.group(*indices)
def groups(default=None):
if last_match is None:
raise TypeError, "No successful match yet."
return last_match.groups(default)
def groupdict(default=None):
if last_match is None:
raise TypeError, "No successful match yet."
return last_match.groupdict(default)
def start(group=0):
if last_match is None:
raise TypeError, "No successful match yet."
return last_match.start(group)
def end(group=0):
if last_match is None:
raise TypeError, "No successful match yet."
return last_match.end(group)
def span(group=0):
if last_match is None:
raise TypeError, "No successful match yet."
return last_match.span(group)
del wraps # Not needed past module compilation
Por ejemplo:
if gre.match("foo(.+)", var):
# do something with gre.group(1)
elif gre.match("bar(.+)", var):
# do something with gre.group(1)
elif gre.match("baz(.+)", var):
# do something with gre.group(1)