正则表达式 Python / 组量词
Posted
技术标签:
【中文标题】正则表达式 Python / 组量词【英文标题】:Regex Python / group quantifiers 【发布时间】:2011-10-12 00:25:44 【问题描述】:我想匹配一个看起来像目录的变量列表,例如:
Same/Same2/Foot/Ankle/Joint/Actuator/Sensor/Temperature/Value=4.123
Same/Same2/Battery/Name=SomeString
Same/Same2/Home/Land/Some/More/Stuff=0.34
“子目录”的长度是可变的,具有上限(高于 9)。 我想对除上面我命名为“相同”的第一个子目录之外的每个子目录进行分组。
我能想到的最好的方法是:
^(?:([^/]+)/)4,8([^/]+)=(.*)
它已经查找了 4-8 个子目录,但只对最后一个进行分组。为什么? 使用组量词有更好的解决方案吗?
编辑:已解决。将使用 split() 代替。
【问题讨论】:
你真的需要正则表达式吗? 有什么理由不能使用split
和其他常规Python 字符串管理函数来代替使用正则表达式?
该列表包含数千个这样的目录,这些目录将按照特定约定解析为变量名,例如Same/Same2/Battery/Name=SomeString 变为 SAME2_BATTERY_NAME=SomeString。有没有比正则表达式更好的方法?
嗯。谢谢,从来没想过分手。使它更容易和更快! :)
顺便说一句,您的解决方案不起作用,因为使用重复的组模式,只返回最后一个匹配项
【参考方案1】:
import re
regx = re.compile('(?:(?<=\A)|(?<=/)).+?(?=/|\Z)')
for ss in ('Same/Same2/Foot/Ankle/Joint/Actuator/Sensor/Temperature/Value=4.123',
'Same/Same2/Battery/Name=SomeString',
'Same/Same2/Home/Land/Some/More/Stuff=0.34'):
print ss
print regx.findall(ss)
print
编辑 1
现在您已经提供了有关您想要获得的更多信息(_"Same/Same2/Battery/Name=SomeString 变为 SAME2_BATTERY_NAME=SomeString"_ )可以提出更好的解决方案:使用正则表达式或使用 split() , + 替换()
import re
from os import sep
sep2 = r'\\' if sep=='\\' else '/'
pat = '^(?:.+?%s)(.+$)' % sep2
print 'pat==%s\n' % pat
ragx = re.compile(pat)
for ss in ('Same\Same2\Foot\Ankle\Joint\Actuator\Sensor\Temperature\Value=4.123',
'Same\Same2\Battery\Name=SomeString',
'Same\Same2\Home\Land\Some\More\Stuff=0.34'):
print ss
print ragx.match(ss).group(1).replace(sep,'_')
print ss.split(sep,1)[1].replace(sep,'_')
print
结果
pat==^(?:.+?\\)(.+$)
Same\Same2\Foot\Ankle\Joint\Actuator\Sensor\Temperature\Value=4.123
Same2_Foot_Ankle_Joint_Actuator_Sensor_Temperature_Value=4.123
Same2_Foot_Ankle_Joint_Actuator_Sensor_Temperature_Value=4.123
Same\Same2\Battery\Name=SomeString
Same2_Battery_Name=SomeString
Same2_Battery_Name=SomeString
Same\Same2\Home\Land\Some\More\Stuff=0.34
Same2_Home_Land_Some_More_Stuff=0.34
Same2_Home_Land_Some_More_Stuff=0.34
编辑 2
重新阅读您的评论,我意识到我没有考虑到您想要将位于“=”符号之前但不在其之后的字符串部分加高。
因此,这个新代码公开了 3 种满足此要求的方法。您将选择您喜欢的:
import re
from os import sep
sep2 = r'\\' if sep=='\\' else '/'
pot = '^(?:.+?%s)(.+?)=([^=]*$)' % sep2
print 'pot==%s\n' % pot
rogx = re.compile(pot)
pet = '^(?:.+?%s)(.+?(?==[^=]*$))' % sep2
print 'pet==%s\n' % pet
regx = re.compile(pet)
for ss in ('Same\Same2\Foot\Ankle\Joint\Sensor\Value=4.123',
'Same\Same2\Battery\Name=SomeString',
'Same\Same2\Ocean\Atlantic\North=',
'Same\Same2\Maths\Addition\\2+2=4\Simple=ohoh'):
print ss + '\n' + len(ss)*'-'
print 'rogx groups '.rjust(32),rogx.match(ss).groups()
a,b = ss.split(sep,1)[1].rsplit('=',1)
print 'split split '.rjust(32),(a,b)
print 'split split join upper replace %s=%s' % (a.replace(sep,'_').upper(),b)
print 'regx split group '.rjust(32),regx.match(ss.split(sep,1)[1]).group()
print 'regx split sub '.rjust(32),\
regx.sub(lambda x: x.group(1).replace(sep,'_').upper(), ss)
print
结果,在 Windows 平台上
pot==^(?:.+?\\)(.+?)=([^=]*$)
pet==^(?:.+?\\)(.+?(?==[^=]*$))
Same\Same2\Foot\Ankle\Joint\Sensor\Value=4.123
----------------------------------------------
rogx groups ('Same2\\Foot\\Ankle\\Joint\\Sensor\\Value', '4.123')
split split ('Same2\\Foot\\Ankle\\Joint\\Sensor\\Value', '4.123')
split split join upper replace SAME2_FOOT_ANKLE_JOINT_SENSOR_VALUE=4.123
regx split group Same2\Foot\Ankle\Joint\Sensor\Value
regx split sub SAME2_FOOT_ANKLE_JOINT_SENSOR_VALUE=4.123
Same\Same2\Battery\Name=SomeString
----------------------------------
rogx groups ('Same2\\Battery\\Name', 'SomeString')
split split ('Same2\\Battery\\Name', 'SomeString')
split split join upper replace SAME2_BATTERY_NAME=SomeString
regx split group Same2\Battery\Name
regx split sub SAME2_BATTERY_NAME=SomeString
Same\Same2\Ocean\Atlantic\North=
--------------------------------
rogx groups ('Same2\\Ocean\\Atlantic\\North', '')
split split ('Same2\\Ocean\\Atlantic\\North', '')
split split join upper replace SAME2_OCEAN_ATLANTIC_NORTH=
regx split group Same2\Ocean\Atlantic\North
regx split sub SAME2_OCEAN_ATLANTIC_NORTH=
Same\Same2\Maths\Addition\2+2=4\Simple=ohoh
-------------------------------------------
rogx groups ('Same2\\Maths\\Addition\\2+2=4\\Simple', 'ohoh')
split split ('Same2\\Maths\\Addition\\2+2=4\\Simple', 'ohoh')
split split join upper replace SAME2_MATHS_ADDITION_2+2=4_SIMPLE=ohoh
regx split group Same2\Maths\Addition\2+2=4\Simple
regx split sub SAME2_MATHS_ADDITION_2+2=4_SIMPLE=ohoh
【讨论】:
太棒了。感谢您提供了一个如此优雅地展示这两种方法的解决方案!【参考方案2】:我可能误解了你到底想做什么,但这里是你在没有正则表达式的情况下如何做到的:
for entry in list_of_vars:
key, value = entry.split('=')
key_components = key.split('/')
if 4 <= len(key_components) <= 8:
# here the actual work is done
print "%s=%s" % ('_'.join(key_components[1:]).upper(), value)
【讨论】:
@hop 我看不出有兴趣根据'='进行拆分以获得然后拆分的键值。函数 split() 有一个默认参数,可以设置为 1 以仅在第一个 '/' 上拆分【参考方案3】:只使用拆分?
>>> p='Same/Same2/Foot/Ankle/Joint/Actuator/Sensor/Temperature/Value=4.123'
>>> p.split('/')
['Same', 'Same2', 'Foot', 'Ankle', 'Joint', 'Actuator', 'Sensor', 'Temperature', 'Value=4.123']
另外,如果你想要那个 key/val 对,你可以做这样的事情......
>>> s = p.split('/')
>>> s[-1].split('=')
['Value', '4.123']
【讨论】:
我同意这种做法。然而,变量的最终列表应该是p.split('/')[1:]
,因为忽略了初始的 Same
值。【参考方案4】:
您的主题有几个变体。一方面,我一直发现 regexen 神秘到无法维护的地步,所以我编写了 pyparsing 模块。在我的脑海中,我看着你的代码并想,“哦,这是一个'/'分隔的字符串列表,一个'='符号,然后是某种右值。”这可以直接转化为 pyparsing 解析器定义代码。通过在解析器中到处添加名称(“键”和“值”,类似于正则表达式中的命名组),输出很容易处理。
data="""\
Same/Same2/Foot/Ankle/Joint/Actuator/Sensor/Temperature/Value=4.123
Same/Same2/Battery/Name=SomeString
Same/Same2/Home/Land/Some/More/Stuff=0.34""".splitlines()
from pyparsing import Word, alphas, alphanums, Word, nums, QuotedString, delimitedList
wd = Word(alphas, alphanums)
number = Word(nums+'+-', nums+'.').setParseAction(lambda t:float(t[0]))
rvalue = wd | number | QuotedString('"')
defn = delimitedList(wd, '/')('key') + '=' + rvalue('value')
for d in data:
result = defn.parseString(d)
其次,我质疑您定义所有这些变量名称的方法 - 根据您的数据动态创建变量名称是一种公认的 Code Smell(不一定很糟糕,但您可能真的 想重新考虑这种方法)。我使用递归 defaultdict 来创建一个可导航的结构,以便您可以轻松地执行诸如“查找所有属于“Same2”子元素的条目(在本例中为“Foot”、“Battery”和“Home”)之类的操作- 当试图筛选在 locals() 中找到的一些变量名集合时,这种工作会更加困难,在我看来,您最终将重新解析这些名称以重建密钥层次结构。
from collections import defaultdict
class recursivedefaultdict(defaultdict):
def __init__(self, attrFactory=int):
self.default_factory = lambda : type(self)(attrFactory)
self._attrFactory = attrFactory
def __getattr__(self, attr):
newval = self._attrFactory()
setattr(self, attr, newval)
return newval
table = recursivedefaultdict()
# parse each entry, and accumulate into hierarchical dict
for d in data:
# use pyparsing parser, gives us key (list of names) and value
result = defn.parseString(d)
t = table
for k in result.key[:-1]:
t = t[k]
t[result.key[-1]] = result.value
# recursive method to iterate over hierarchical dict
def showTable(t, indent=''):
for k,v in t.items():
print indent+k,
if isinstance(v,dict):
print
showTable(v, indent+' ')
else:
print v
showTable(table)
打印:
Same
Same2
Foot
Ankle
Joint
Actuator
Sensor
Temperature
Value 4.123
Battery
Name SomeString
Home
Land
Some
More
Stuff 0.34
如果您真的决定定义这些变量名称,那么在 pyparsing 中添加一些有用的解析操作将在解析时重新格式化解析的数据,以便之后可以直接处理:
wd = Word(alphas, alphanums)
number = Word(nums+'+-', nums+'.').setParseAction(lambda t:float(t[0]))
rvaluewd = wd.copy().setParseAction(lambda t: '"%s"' % t[0])
rvalue = rvaluewd | number | QuotedString('"')
defn = delimitedList(wd, '/')('key') + '=' + rvalue('value')
def joinNamesWithAllCaps(tokens):
tokens["key"] = '_'.join(map(str.upper, tokens.key))
defn.setParseAction(joinNamesWithAllCaps)
for d in data:
result = defn.parseString(d)
print result.key,'=', result.value
打印:
SAME_SAME2_FOOT_ANKLE_JOINT_ACTUATOR_SENSOR_TEMPERATURE_VALUE = 4.123
SAME_SAME2_BATTERY_NAME = "SomeString"
SAME_SAME2_HOME_LAND_SOME_MORE_STUFF = 0.34
(请注意,这也将您的 SomeString 值括在引号中,以便生成的赋值语句是有效的 Python。)
【讨论】:
以上是关于正则表达式 Python / 组量词的主要内容,如果未能解决你的问题,请参考以下文章