Разобрать строку с трехуровневым разделителем в словарь

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

clientid=b59694bf-c7c1-4a3a-8cd5-6dad69f4abb0&keyid=987654321&userdata=ip:192.168.10.10,deviceid:1234,optdata:75BCD15&md=AMT-Cam:avatar&playbackmode=st&ver=6&sessionid=&mk=PC&junketid=1342177342&version=6.7.8.9012

Очевидно, что здесь это фиктивные параметры для запутывания проприетарного кода. Я хотел бы сбросить все это в словарь со значениями ключей userdata и md, которые сами являются словарями:

requestdict {'clientid' : 'b59694bf-c7c1-4a3a-8cd5-6dad69f4abb0', 'keyid' : '987654321', 'userdata' : {'ip' : '192.168.10.10', 'deviceid' : '1234', 'optdata' : '75BCD15'}, 'md' : {'Cam' : 'avatar'}, 'playbackmode' : 'st', 'ver' : '6', 'sessionid' : '', 'mk' : 'PC', 'junketid' : '1342177342', 'version' : '6.7.8.9012'}

Могу ли я взять гладкую двухуровневую команду разбора разграничения, которую я нашел:

requestDict = dict(line.split('=') for line in clientRequest.split('&'))

и добавить к нему третий уровень для обработки и сохранения словарей 2-го уровня? Какой будет синтаксис? Если нет, я полагаю, мне придется разбить на &, а затем проверить и обработать разбиения, содержащие :, но даже тогда я не могу понять синтаксис. Кто-нибудь может помочь? Спасибо!


person Jacob Stevens    schedule 06.12.2012    source источник
comment
Возможно, вы захотите сохранить этот ключ 'sessionid' как None.   -  person Droogans    schedule 07.12.2012


Ответы (3)


В основном я взял ответ Кайла и сделал его более удобным для будущего:

def dictelem(input):   
    parts   = input.split('&')
    listing = [part.split('=') for part in parts]

    result = {}
    for entry in listing:
        head, tail = entry[0], ''.join(entry[1:])
        if ':' in tail:
            entries = tail.split(',')
            result.update({ head : dict(e.split(':') for e in entries) })
        else:
            result.update({head: tail})

    return result
person Droogans    schedule 06.12.2012
comment
Большое спасибо вам обоим. - person Jacob Stevens; 11.12.2012

Вот двухстрочный, который делает то, что, как я думаю, вы хотите:

dictelem = lambda x: x if ':' not in x[1] else [x[0],dict(y.split(':') for y in x[1].split(','))]
a = dict(dictelem(x.split('=')) for x in input.split('&'))
person Kyle Strand    schedule 06.12.2012
comment
Я сделал свою собственную версию этого умопомрачительного решения. Вероятно, не смог бы получить его, не скопировав вас, однако ;). - person Droogans; 07.12.2012
comment
Спасибо! Да, для чего-то такого сложного действительно гораздо лучше четко указать, что происходит, чем пытаться быть умно кратким, что является моей дурной привычкой. - person Kyle Strand; 07.12.2012
comment
На самом деле, теперь, когда я это прочитал, единственное, что не читается, — это индексация одного элемента вашего параметра x. Все остальное выглядит довольно просто, лаконично. - person Droogans; 07.12.2012

Могу ли я взять гладкую двухуровневую команду разбора разграничения, которую я нашел:

requestDict = dict(line.split('=') for line in clientRequest.split('&'))

и добавить к нему третий уровень для обработки и сохранения словарей 2-го уровня?

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

Например, что должно произойти с 'PC'? Вы хотите превратить это в {'PC': None}? Или, может быть, set {'PC'}? Или list ['PC']? Или просто оставить в покое? Вы должны решить и написать логику для этого, и попытка написать это в виде выражения сделает ваше решение очень трудным для чтения.

Итак, давайте поместим эту логику в отдельную функцию:

def parseCommasAndColons(s):
    bits = [bit.split(':') for bit in s.split(',')]
    try:
        return dict(bits)
    except ValueError:
        return bits

Это вернет dict, например {'ip': '192.168.10.10', 'deviceid': '1234', 'optdata': '75BCD15'} или {'AMT-Cam': 'avatar'}, для случаев, когда внутри каждого компонента, разделенного запятыми, есть двоеточие, и list, например ['1342177342'], для случаев, когда ни один из них не имеет двоеточия.

Даже это может быть слишком умно; Я мог бы сделать проверку «это в формате словаря» более явной, вместо того, чтобы просто пытаться преобразовать список списков и посмотреть, что произойдет.

В любом случае, как бы вы вернули это в исходное понимание?

Ну, вы хотите вызвать его по значению в line.split('='). Итак, давайте добавим для этого функцию:

def parseCommasAndColonsForValue(keyvalue):
    if len(keyvalue) == 2:
        return keyvalue[0], parseCommasAndColons(keyvalue[1])
    else:
        return keyvalue

requestDict = dict(parseCommasAndColonsForValue(line.split('=')) 
                   for line in clientRequest.split('&'))

И последнее: если вам не нужно работать на более старых версиях Python, вам не следует часто вызывать dict в выражении генератора. Если его можно переписать как понимание словаря, оно почти наверняка будет более ясным, а если его нельзя переписать как понимание словаря, то, вероятно, оно не должно быть однострочным выражением в первую очередь.

Конечно, разбиение выражений на отдельные выражения, превращение некоторых из них в операторы или даже функции и присвоение им имен делает ваш код длиннее, но это не обязательно означает, что он хуже. Около половины Дзен Питона (import this) посвящено объяснению почему. Или одна цитата из Гвидо: «Python — это плохой язык для гольфа, и это намеренно».

Если вы действительно хотите знать, как это будет выглядеть, давайте разобьем его на два шага:

>>> {k: [bit2.split(':') for bit2 in v.split(',')] for k, v in (bit.split('=') for bit in s.split('&'))}
{'clientid': [['b59694bf-c7c1-4a3a-8cd5-6dad69f4abb0']],
 'junketid': [['1342177342']],
 'keyid': [['987654321']],
 'md': [['AMT-Cam', 'avatar']],
 'mk': [['PC']],
 'playbackmode': [['st']],
 'sessionid': [['']],
 'userdata': [['ip', '192.168.10.10'],
              ['deviceid', '1234'],
              ['optdata', '75BCD15']],
 'ver': [['6']],
 'version': [['6.7.8.9012']]}

Это иллюстрирует, почему вы не можете просто добавить вызов dict для внутреннего уровня, потому что большинство этих вещей на самом деле не словари, потому что в них нет двоеточий. Если бы вы изменили это, то это было бы просто так:

{k: dict(bit2.split(':') for bit2 in v.split(',')) for k, v in (bit.split('=') for bit in s.split('&'))}

Я не думаю, что это очень читабельно, и я сомневаюсь, что большинство программистов на Python справятся. Прочитать его через 6 месяцев и попытаться понять, что я имел в виду, потребовало бы гораздо больше усилий, чем его написание.

И пытаться его отлаживать будет не весело. Что произойдет, если вы запустите это на своем входе с отсутствующими двоеточиями? ValueError: dictionary update sequence element #0 has length 1; 2 is required. Какая последовательность? Без понятия. Вы должны разбить его шаг за шагом, чтобы увидеть, что не работает. Это не весело.

Итак, надеюсь, это иллюстрирует, почему вы не хотите этого делать.

person abarnert    schedule 06.12.2012
comment
Большое спасибо за обоснование конструкции, очень признателен. - person Jacob Stevens; 11.12.2012