Простой способ конвертировать строку в словарь - PullRequest
6 голосов
/ 29 ноября 2009

Какой самый простой способ преобразовать строку слов = значений в словарь, например следующую строку:

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

в следующий словарь Python:

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

Клавиша "аватар" просто показывает, что строки могут содержать = и, поэтому простое разделение не подойдет. Есть идеи? Спасибо!

Ответы [ 10 ]

9 голосов
/ 29 ноября 2009

Это работает для меня:

# 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)
4 голосов
/ 29 ноября 2009

Редактировать : поскольку модуль 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, но это не так, и не может быть (так как IS нет Значение с плавающей запятой, которое будет отображать 173.2 в таком случае! -), когда print применяется ко всему dict (потому что это неизбежно использует repr для ключей и значений - и repr из 173.2 имеет эту форму, учитывая обычные проблемы с тем, как значения с плавающей запятой хранятся в двоичном, а не в десятичном виде и т. Д. И т. Д.). Я мог бы определить подкласс dict, который переопределяет __str__ на специальные значения с плавающей запятой, если это действительно требуется.

Но, я надеюсь, что это отвлечение не мешает основной идее - до тех пор, пока двойные кавычки правильно сбалансированы (и нет двойных кавычек внутри двойных кавычек), этот код выполняет требуемую задачу сохранения "специального символы "(в данном случае запятые и знаки равенства) взяты в обычном смысле, когда они находятся в двойных кавычках, даже если двойные кавычки начинаются с внутри " поля ", а не с начала поле (csv имеет дело только с последним условием). Вставьте несколько промежуточных оттисков, если способ работы кода неочевиден - сначала он превращает все «поля в двойных кавычках» в особо простую форму ("0", "1" и т. Д.), Одновременно записывая то, что является фактическим содержимым соответствующие этим простым формам; в конце простые формы возвращаются в исходное содержание. Обрезка в двойных кавычках (для строк) и преобразование строк без кавычек в целые числа или числа с плавающей запятой, наконец, обрабатываются простой функцией converter.

2 голосов
/ 29 ноября 2009

Вот более подробный подход к проблеме с использованием pyparsing. Обратите внимание на действия разбора которые делают автоматическое преобразование типов из строк в int или float. Так же Класс 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)))

Теперь, чтобы проанализировать вашу исходную текстовую строку, сохраняя результаты в дд. Pyparsing возвращает объект типа ParseResults, но этот класс имеет много функций, похожих на dict (поддержка keys (), items (), in и т. д.) или может выдать истинный диктант Python, вызвав asDict () Вызов дампа () показывает все токены в исходном разобранном списке, а также все именованные элементы. Последний два примера показывают, как получить доступ к именованным элементам в 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 голос
/ 17 февраля 2018

Я бы предложил ленивый способ сделать это.

test_string = 'name="John Smith", age=34, height=173.2, location="US", avatar=":,=)"'
eval("dict({})".format(test_string))

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

Надеюсь, это кому-нибудь поможет!

1 голос
/ 02 декабря 2009

Вот несколько более надежная версия решения регулярного выражения:

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}
1 голос
/ 29 ноября 2009

Вот подход с 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}
>>>

Обновление:

Лучше использовать тот, который указал Крис Латс в комментарии, я считаю, что он более надежен, потому что даже если в значениях dict есть (одинарные / двойные) кавычки, это может сработать.

1 голос
/ 29 ноября 2009

Следующий код выдает правильное поведение, но только немного длиннее! Я добавил пробел в аватаре, чтобы показать, что он хорошо работает с запятыми и пробелами и знаками равенства внутри строки. Любые предложения, чтобы сократить его?

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    
0 голосов
/ 29 ноября 2009

делай это шаг за шагом

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
0 голосов
/ 29 ноября 2009

Я думаю, вам просто нужно установить 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]) )
0 голосов
/ 29 ноября 2009

Всегда разделены запятой? Используйте модуль CSV, чтобы разбить линию на части (не проверено):

import csv
import cStringIO

parts=csv.reader(cStringIO.StringIO(<string to parse>)).next()
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...