Python,如何解析忽略括号内内容的key=value列表?

3 投票
3 回答
1637 浏览
提问于 2025-04-17 13:01

假设我有一个这样的字符串:

"key1=value1;key2=value2;key3=(key3.1=value3.1;key3.2=value3.2)"

我想得到一个字典,里面的 key3 对应的值是这个字符串

"(key3.1=value3.1;key3.2=value3.2)"

并且最终得到相应的子字典。

我知道怎么在分号处把字符串切开,但我该怎么告诉解析器忽略括号之间的分号呢?
这还包括可能嵌套的括号。

目前我使用的是一种临时的办法,寻找成对的括号,"清除"它们的内容,找到切分的位置,然后把这些位置应用到原始字符串上,但这样做看起来不太优雅,应该有一些现成的、更符合Python风格的方法来实现这个。

如果有人感兴趣,这里是我目前使用的代码:

def pparams(parameters, sep=';', defs='=', brc='()'):
    '''
    unpackages parameter string to struct
    for example, pippo(a=21;b=35;c=pluto(h=zzz;y=mmm);d=2d3f) becomes:
     a: '21'
     b: '35'
     c.fn: 'pluto'
     c.h='zzz'
     d: '2d3f'
     fn_: 'pippo'
    '''

    ob=strfind(parameters,brc[0])
    dp=strfind(parameters,defs)
    out={}

    if len(ob)>0:
        if ob[0]<dp[0]:
            #opening function
            out['fn_']=parameters[:ob[0]]
            parameters=parameters[(ob[0]+1):-1]
    if len(dp)>0:
        temp=smart_tokenize(parameters,sep,brc);
        for v in temp:
            defp=strfind(v,defs)
            pname=v[:defp[0]]
            pval=v[1+defp[0]:]
            if len(strfind(pval,brc[0]))>0:
                out[pname]=pparams(pval,sep,defs,brc);
            else:
                out[pname]=pval
    else:
        out['fn_']=parameters
    return out

def smart_tokenize( instr, sep=';', brc='()' ):
    '''
    tokenize string ignoring separators contained within brc
    '''
    tstr=instr;
    ob=strfind(instr,brc[0])
    while len(ob)>0:
        cb=findclsbrc(tstr,ob[0])
        tstr=tstr[:ob[0]]+'?'*(cb-ob[0]+1)+tstr[cb+1:]
        ob=strfind(tstr,brc[1])
    sepp=[-1]+strfind(tstr,sep)+[len(instr)+1]
    out=[]
    for i in range(1,len(sepp)):
        out.append(instr[(sepp[i-1]+1):(sepp[i])])
    return out

def findclsbrc(instr, brc_pos, brc='()'):
    '''
    given a string containing an opening bracket, finds the
    corresponding closing bracket
    '''
    tstr=instr[brc_pos:]
    o=strfind(tstr,brc[0])
    c=strfind(tstr,brc[1])
    p=o+c
    p.sort()
    s1=[1 if v in o else 0 for v in p]
    s2=[-1 if v in c else 0 for v in p]
    s=[s1v+s2v for s1v,s2v in zip(s1,s2)]
    s=[sum(s[:i+1]) for i in range(len(s))] #cumsum
    return p[s.index(0)]+brc_pos

def strfind(instr, substr):
    '''
    returns starting position of each occurrence of substr within instr
    '''
    i=0
    out=[]
    while i<=len(instr):
        try:
            p=instr[i:].index(substr)
            out.append(i+p)
            i+=p+1
        except:
            i=len(instr)+1
    return out

3 个回答

0

你可以用正则表达式来抓取这些组:

>>> import re
>>> s = "key1=value1;key2=value2;key3=(key3.1=value3.1;key3.2=value3.2)"
>>> r = re.compile('(\w+)=(\w+|\([^)]+\));?')
>>> dict(r.findall(s))

这个正则表达式的意思是:

(\w)+ # Find and capture a group with 1 or more word characters (letters, digits, underscores)
= # Followed by the literal character '='
(\w+ # Followed by a group with 1 or more word characters
|\([^)]+\) # or a group that starts with an open paren (parens escaped with '\(' or \')'), followed by anything up until a closed paren, which terminates the alternate grouping
);? # optionally this grouping might be followed by a semicolon.

说实话,这种语法有点奇怪。你可以考虑使用一种更标准的格式。如果你需要帮助选择,可以再问一个问题。祝你好运!

1

在Sven Marnach的回答基础上,这里有一个例子,展示了一个可以用的pyparsing 语法:

from pyparsing import (ZeroOrMore, Word, printables, Forward,
                       Group, Suppress, Dict)


collection = Forward()
simple_value = Word(printables, excludeChars='()=;')
key = simple_value
inner_collection = Suppress('(') + collection + Suppress(')')
value = simple_value ^ inner_collection
key_and_value = Group(key + Suppress('=') + value)
collection << Dict(key_and_value + ZeroOrMore(Suppress(';') + key_and_value))


coll = collection.parseString(
    "key1=value1;key2=value2;key3=(key3.1=value3.1;key3.2=value3.2)")

print coll['key1']             # value1
print coll['key2']             # value2
print coll['key3']['key3.1']   # value3.1
6

如果你想要建立一个真正的解析器,可以使用一些Python 解析库,比如PLY或者PyParsing。如果你觉得这些完整的库对你当前的任务来说太复杂了,那就可以用一些简单的办法,比如你现在用的那种。我敢肯定,没有外部库的话,很难找到几行干净的代码来解决这个问题。

撰写回答