我在Python中发现的最大烦恼之一是re
模块无法在匹配对象中明确地保存其状态而无法保存其状态。通常,需要解析行,如果它们符合某个正则表达式,则从相同的正则表达式中取出它们的值。我想写这样的代码:
if re.match('foo (\w+) bar (\d+)', line):
# do stuff with .group(1) and .group(2)
elif re.match('baz whoo_(\d+)', line):
# do stuff with .group(1)
# etc.
但遗憾的是,无法找到前一次调用re.match
的匹配对象,所以这样写的是:
m = re.match('foo (\w+) bar (\d+)', line)
if m:
# do stuff with m.group(1) and m.group(2)
else:
m = re.match('baz whoo_(\d+)', line)
if m:
# do stuff with m.group(1)
随着elif
列表的增长,这种方法变得不那么方便而且变得非常笨拙。
一个hackish解决方案是将re.match和re.search包装在我自己的对象中,以保持状态。有没有人用过这个?您是否了解半标准实现(在大型框架或其他内容中)?
您可以推荐哪些其他解决方法? 或者,我是否只是滥用模块并以更清洁的方式实现我的需求?
提前致谢
答案 0 :(得分:5)
您可能希望this module实现您正在寻找的包装器。
答案 1 :(得分:4)
尝试一些想法......
看起来您理想地想要一个带副作用的表达式。如果在Python中允许这样做:
if m = re.match('foo (\w+) bar (\d+)', line):
# do stuff with m.group(1) and m.group(2)
elif m = re.match('baz whoo_(\d+)', line):
# do stuff with m.group(1)
elif ...
...然后你会清楚而干净地表达你的意图。但事实并非如此。如果嵌套函数允许副作用,您可以:
m = None
def assign_m(x):
m = x
return x
if assign_m(re.match('foo (\w+) bar (\d+)', line)):
# do stuff with m.group(1) and m.group(2)
elif assign_m(re.match('baz whoo_(\d+)', line)):
# do stuff with m.group(1)
elif ...
现在,不仅变得丑陋,而且它仍然无效的Python代码 - 嵌套函数'assign_m'不允许修改外部作用域中的变量m
。我能想到的最好的是真的丑陋,使用嵌套类可以产生副作用:
# per Brian's suggestion, a wrapper that is stateful
class m_(object):
def match(self, *args):
self.inner_ = re.match(*args)
return self.inner_
def group(self, *args):
return self.inner_.group(*args)
m = m_()
# now 'm' is a stateful regex
if m.match('foo (\w+) bar (\d+)', line):
# do stuff with m.group(1) and m.group(2)
elif m.match('baz whoo_(\d+)', line):
# do stuff with m.group(1)
elif ...
但明显过度杀伤。
您可以考虑使用内部函数来允许本地范围退出,这允许您删除else
嵌套:
def find_the_right_match():
# now 'm' is a stateful regex
m = re.match('foo (\w+) bar (\d+)', line)
if m:
# do stuff with m.group(1) and m.group(2)
return # <== exit nested function only
m = re.match('baz whoo_(\d+)', line)
if m:
# do stuff with m.group(1)
return
find_the_right_match()
这可以让你将嵌套=(2 * N-1)展平为嵌套= 1,但是你可能刚刚移动了副作用问题,并且嵌套函数很可能会混淆大多数Python程序员。
最后,有无副作用的方法来解决这个问题:
def cond_with(*phrases):
"""for each 2-tuple, invokes first item. the first pair where
the first item returns logical true, result is passed to second
function in pair. Like an if-elif-elif.. chain"""
for (cond_lambda, then_lambda) in phrases:
c = cond_lambda()
if c:
return then_lambda(c)
return None
cond_with(
((lambda: re.match('foo (\w+) bar (\d+)', line)),
(lambda m:
... # do stuff with m.group(1) and m.group(2)
)),
((lambda: re.match('baz whoo_(\d+)', line)),
(lambda m:
... # do stuff with m.group(1)
)),
...)
现在代码几乎不像Python那样看起来像,更不用说Python程序员可以理解了(是Lisp吗?)。
我认为这个故事的寓意是Python并没有针对这种习惯进行优化。你真的需要有点冗长,并且有其他条件的大嵌套因子。
答案 2 :(得分:1)
您可以编写实用程序类来执行“保存状态和返回结果”操作。我不认为这就是那个hackish。实施起来相当简单:
class Var(object):
def __init__(self, val=None): self.val = val
def set(self, result):
self.val = result
return result
然后将其用作:
lastMatch = Var()
if lastMatch.set(re.match('foo (\w+) bar (\d+)', line)):
print lastMatch.val.groups()
elif lastMatch.set(re.match('baz whoo_(\d+)', line)):
print lastMatch.val.groups()
答案 3 :(得分:1)
class last(object):
def __init__(self, wrapped, initial=None):
self.last = initial
self.func = wrapped
def __call__(self, *args, **kwds):
self.last = self.func(*args, **kwds)
return self.last
def test():
"""
>>> test()
crude, but effective: (oYo)
"""
import re
m = last(re.compile("(oYo)").match)
if m("abc"):
print("oops")
elif m("oYo"): #A
print("crude, but effective: (%s)" % m.last.group(1)) #B
else:
print("mark")
if __name__ == "__main__":
import doctest
doctest.testmod()
last
也适合作为装饰者。
意识到我努力使其自我测试并在2.5,2.6和3.0中工作,我在某种程度上模糊了真正的解决方案。重要的行标记为上面的#A和#B,您可以使用相同的对象进行测试(将其命名为match
或is_somename
)并检索其最后一个值。易于滥用,但也很容易调整,如果没有推得太远,可以获得令人惊讶的清晰代码。
答案 4 :(得分:1)
基于对这个问题的重大答案,我编造了以下机制。它似乎是解决Python中“无条件限制”限制的一般方法。重点是透明度,由无声授权实施:
class Var(object):
def __init__(self, val=None):
self._val = val
def __getattr__(self, attr):
return getattr(self._val, attr)
def __call__(self, arg):
self._val = arg
return self._val
if __name__ == "__main__":
import re
var = Var()
line = 'foo kwa bar 12'
if var(re.match('foo (\w+) bar (\d+)', line)):
print var.group(1), var.group(2)
elif var(re.match('baz whoo_(\d+)', line)):
print var.group(1)
在一般情况下,这是一个线程安全的解决方案,因为您可以创建自己的Var
实例。为了在线程不成问题时更易于使用,可以导入和使用默认的Var对象。这是一个包含Var类的模块:
class Var(object):
def __init__(self, val=None):
self._val = val
def __getattr__(self, attr):
return getattr(self._val, attr)
def __call__(self, arg):
self._val = arg
return self._val
var = Var()
这是用户的代码:
from var import Var, var
import re
line = 'foo kwa bar 12'
if var(re.match('foo (\w+) bar (\d+)', line)):
print var.group(1), var.group(2)
elif var(re.match('baz whoo_(\d+)', line)):
print var.group(1)
虽然不是线程安全的,但对于许多简单的脚本,这提供了一个有用的快捷方式。
答案 5 :(得分:1)
Python 3.8现在为我们提供了一个简洁的解决方案::=
(walrus operator)。
它将右侧的值分配给左侧的变量,然后返回该值。
基本上,我们终于可以实现@aaron的愿望,只需写:
if m := re.match('foo (\w+) bar (\d+)', line):
# do stuff with m.group(1) and m.group(2)
elif m := re.match('baz whoo_(\d+)', line):
# do stuff with m.group(1)
elif ...
答案 6 :(得分:0)
可能最简单的解决方案是尽早返回,这样您就可以回到创建变量而不需要立即进行测试。
def get_results(line):
m = re.match('foo (\w+) bar (\d+)', line)
if m:
# do stuff with .group(1) and .group(2)
return result
m = re.match('baz whoo_(\d+)', line)
if m:
# do stuff with .group(1)
return other_result
# etc.
这样可以避免过度嵌套。