将字符串转换为字典的简单方法

7

如何将一个关键字=值字符串简单地转换成字典?例如下面的字符串:

name="John Smith", age=34, height=173.2, location="US", avatar=":,=)"

转换为以下Python字典:

{'name':'John Smith', 'age':34, 'height':173.2, 'location':'US', 'avatar':':,=)'}

“avatar”键只是为了说明字符串可以包含=和,,因此简单的“split”方法行不通。有什么想法吗?谢谢!
10个回答

9
这对我有用:

这对我有用:

# get all the items
matches = re.findall(r'\w+=".+?"', s) + re.findall(r'\w+=[\d.]+',s)

# partition each match at '='
matches = [m.group().split('=', 1) for m in matches]

# use results to make a dict
d = dict(matches)

这个可以工作 - 只需添加将最终值转换为字符串/整数等的例程,以及可能剥离值中包含的不需要的双引号。 - twneale
非常好,谢谢!我知道正则表达式会是答案,只是从来没有成功地学会如何高效地使用它们! - astrofrog
5
相信我,朋友,它们值得你付出努力。找一个好的交互式正则表达式测试工具(像redemo.py),开始实践吧! - twneale
3
请注意,有些字符串会导致上述正则表达式解决方案出现奇怪的问题,例如avatar="p=0"或更糟糕的是avatar="age=123"。 如果这些问题让您担心,您将需要一个基于解析器的解决方案。顺便说一句,我不知道您是否对输入格式具有任何控制权,但 JSON 格式非常接近上述输入格式,并且几乎每种语言都有模块来解析它。http://json.org/ - Nick Craig-Wood

4
我建议采用一种简易的方法来做这件事。
test_string = 'name="John Smith", age=34, height=173.2, location="US", avatar=":,=)"'
eval("dict({})".format(test_string))

{'年龄':34, '所在地':'美国', '头像': ':,=)', '姓名': '约翰·史密斯', '身高': 173.2}

希望能对大家有所帮助!


如果可能的话,应避免使用eval。在现实世界中,test_string可能是用户输入,恶意用户可能会滥用它。 - Nico Schlömer

4

编辑:由于csv模块不能很好地处理字段内的引号,因此需要更多的工作来实现这个功能:

import re
quoted = re.compile(r'"[^"]*"')

class QuoteSaver(object):

  def __init__(self):
    self.saver = dict()
    self.reverser = dict()

  def preserve(self, mo):
    s = mo.group()
    if s not in self.saver:
      self.saver[s] = '"%d"' % len(self.saver)
      self.reverser[self.saver[s]] = s
    return self.saver[s]

  def expand(self, mo):
    return self.reverser[mo.group()]

x = 'name="John Smith", age=34, height=173.2, location="US", avatar=":,=)"'

qs = QuoteSaver()
y = quoted.sub(qs.preserve, x)
kvs_strings = y.split(',')
kvs_pairs = [kv.split('=') for kv in kvs_strings]
kvs_restored = [(k, quoted.sub(qs.expand, v)) for k, v in kvs_pairs]

def converter(v):
  if v.startswith('"'): return v.strip('"')
  try: return int(v)
  except ValueError: return float(v)

thedict = dict((k.strip(), converter(v)) for k, v in kvs_restored)
for k in thedict:
  print "%-8s %s" % (k, thedict[k])
print thedict

我将thedict输出两次,以展示它与所需结果的区别及原因; 输出结果如下:

age      34
location US
name     John Smith
avatar   :,=)
height   173.2
{'age': 34, 'location': 'US', 'name': 'John Smith', 'avatar': ':,=)',
 'height': 173.19999999999999}

正如您所看到的,使用print直接输出浮点数值时,输出结果与要求的一样。但是当应用于整个dict时(因为这不可避免地会在键和值上使用repr,而173.2repr具有该形式,考虑到浮点数值如何以二进制而非十进制等等问题),它不是,也不能是(因为在这种情况下没有浮点数值会显示173.2!)。如果确实需要这样做,您可以定义一个dict子类,覆盖__str__以特别处理浮点数值。

但是,我希望这种分心不会干扰核心思想——只要双引号正确平衡(且没有双引号嵌套),此代码确实执行了保留“特殊字符”(在本例中为逗号和等号)不被其正常意义解释的所需任务,即使双引号开始于“字段”的内部而非字段的开头(csv仅处理后者条件)。如果代码的工作方式不明显,请插入一些中间打印——首先将所有“双引号括起来的字段”更改为特别简单的形式(例如"0""1"等),同时单独记录对应于这些简单形式的实际内容;最后,将简单形式更改回原始内容。双引号剥离(用于字符串)和未引用字符串转换为整数或浮点数最终由简单的converter函数处理。


至于Managu的类似解决方案,如果右侧的字符串包含逗号(在我正在处理的情况下确实如此),则无法正常工作。 - astrofrog
你说得对 == CSV 无法理解字段“中间”的引号。让我想出其他办法并修正我的答案。 - Alex Martelli

2

这里是使用pyparsing更详细的方法来解决问题。请注意,解析操作会自动将类型从字符串转换为整数或浮点数。此外,QuotedString类隐式地从引号中删除引号中的值。最后,Dict类将逗号分隔列表中的每个'key = val'组,并使用键和值标记分配结果名称。

from pyparsing import *

key = Word(alphas)
EQ = Suppress('=')
real = Regex(r'[+-]?\d+\.\d+').setParseAction(lambda t:float(t[0]))
integer = Regex(r'[+-]?\d+').setParseAction(lambda t:int(t[0]))
qs = QuotedString('"')
value = real | integer | qs

dictstring = Dict(delimitedList(Group(key + EQ + value)))

现在要解析您的原始文本字符串,并将结果存储在dd中。 Pyparsing返回类型为ParseResults的对象,但该类具有许多类似于字典的功能(支持keys()、items()、in等),或者可以通过调用asDict()来发出真正的Python字典。 调用dump()显示原始解析列表中的所有标记,以及所有命名项。 最后两个示例展示了如何访问ParseResults中的命名项,就像它们是Python对象的属性一样。
text = 'name="John Smith", age=34, height=173.2, location="US", avatar=":,=)"'
dd = dictstring.parseString(text)
print dd.keys()
print dd.items()
print dd.dump()
print dd.asDict()
print dd.name
print dd.avatar

输出:

['age', 'location', 'name', 'avatar', 'height']
[('age', 34), ('location', 'US'), ('name', 'John Smith'), ('avatar', ':,=)'), ('height', 173.19999999999999)]
[['name', 'John Smith'], ['age', 34], ['height', 173.19999999999999], ['location', 'US'], ['avatar', ':,=)']]
- age: 34
- avatar: :,=)
- height: 173.2
- location: US
- name: John Smith
{'age': 34, 'height': 173.19999999999999, 'location': 'US', 'avatar': ':,=)', 'name': 'John Smith'}
John Smith
:,=)

1
以下代码可以产生正确的行为,但有点长!我在头像中添加了一个空格,以显示它可以很好地处理字符串中的逗号、空格和等号。有什么建议可以缩短它吗?
import hashlib

string = 'name="John Smith", age=34, height=173.2, location="US", avatar=":, =)"'

strings = {}

def simplify(value):
    try:
        return int(value)
    except:
        return float(value)

while True:
    try:
        p1 = string.index('"')
        p2 = string.index('"',p1+1)
        substring = string[p1+1:p2]
        key = hashlib.md5(substring).hexdigest()
        strings[key] = substring
        string = string[:p1] + key + string[p2+1:]
    except:
        break

d = {}    
for pair in string.split(', '):
    key, value = pair.split('=')
    if value in strings:
        d[key] = strings[value]
    else:
        d[key] = simplify(value)

print d    

1

这里有一种使用eval的方法,虽然我认为它不太可靠,但对于你的例子来说是有效的。

>>> import re
>>>
>>> s='name="John Smith", age=34, height=173.2, location="US", avatar=":,=)"'
>>>
>>> eval("{"+re.sub('(\w+)=("[^"]+"|[\d.]+)','"\\1":\\2',s)+"}")
{'age': 34, 'location': 'US', 'name': 'John Smith', 'avatar': ':,=)', 'height': 173.19999999999999}
>>>

更新:

最好使用Chris Lutz在评论中指出的那个,我相信它更可靠,因为即使字典值中有(单/双)引号,它也可能有效。


1
如果您要使用 eval ,为什么不直接使用 eval("dict(" + s + ")")?在 Python 已经支持这种语法的情况下,我们不需要进行任何正则表达式替换。 - Chris Lutz

1
这是一个稍微更健壮的正则表达式解决方案:
import re

keyval_re = re.compile(r'''
   \s*                                  # Leading whitespace is ok.
   (?P<key>\w+)\s*=\s*(                 # Search for a key followed by..
       (?P<str>"[^"]*"|\'[^\']*\')|     #   a quoted string; or
       (?P<float>\d+\.\d+)|             #   a float; or
       (?P<int>\d+)                     #   an int.
   )\s*,?\s*                            # Handle comma & trailing whitespace.
   |(?P<garbage>.+)                     # Complain if we get anything else!
   ''', re.VERBOSE)

def handle_keyval(match):
    if match.group('garbage'):
        raise ValueError("Parse error: unable to parse: %r" %
                         match.group('garbage'))
    key = match.group('key')
    if match.group('str') is not None:
        return (key, match.group('str')[1:-1]) # strip quotes
    elif match.group('float') is not None:
        return (key, float(match.group('float')))
    elif match.group('int') is not None:
        return (key, int(match.group('int')))

它会自动将浮点数和整数转换为正确的类型;处理单引号和双引号;处理各种位置的多余空格;如果提供了格式不正确的字符串,则会发出警告。

>>> s='name="John Smith", age=34, height=173.2, location="US", avatar=":,=)"'
>>> print dict(handle_keyval(m) for m in keyval_re.finditer(s))
{'age': 34, 'location': 'US', 'name': 'John Smith', 'avatar': ':,=)', 'height': 173.19999999999999}

0

我认为你只需要设置maxsplit=1,例如以下代码应该可以正常工作。

string = 'name="John Smith", age=34, height=173.2, location="US", avatar=":, =)"'
newDict = dict(map( lambda(z): z.split("=",1), string.split(", ") ))

编辑(见评论):

我没有注意到“,”是头像下的一个值,最好的方法是在生成数据时转义“,”。更好的方法是使用JSON ;)。但是,作为正则表达式的替代方案,您可以尝试使用shlex,我认为它会产生更清晰的代码。

import shlex

string = 'name="John Smith", age=34, height=173.2, location="US", avatar=":, =)"'
lex = shlex.shlex ( string ) 
lex.whitespace += "," # Default whitespace doesn't include commas
lex.wordchars += "."  # Word char should include . to catch decimal 
words = [ x for x in iter( lex.get_token, '' ) ]
newDict = dict ( zip( words[0::3], words[2::3]) )

它给了我这个{'': ')"', 'name': '"John Smith"', 'age': '34', 'height': '173.2', 'location': '"US"', 'avatar': '":'} - YOU

0

一步一步地做

d={}
mystring='name="John Smith", age=34, height=173.2, location="US", avatar=":,=)"';
s = mystring.split(", ")
for item in s:
    i=item.split("=",1)
    d[i[0]]=i[-1]
print d

-2

始终使用逗号分隔?使用 CSV 模块将行拆分为部分(未经检查):

import csv
import cStringIO

parts=csv.reader(cStringIO.StringIO(<string to parse>)).next()

在右侧字符串包含逗号的情况下,这种方法无法正常工作,例如上面的“avatar”案例。但是,如果逗号在引号内,则只会出现在右侧,因此可能可以考虑这一点? - astrofrog
如果您使用正确的方言,CSV应该考虑到它。 - Nick Bastin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接