Python — распаковка кортежа при понимании dict

Я пытаюсь написать функцию, которая превращает строки вида 'A=5, b=7' в словарь {'A': 5, 'b': 7}. Следующие фрагменты кода — это то, что происходит внутри основного цикла for — они превращают одну часть строки в один элемент dict.

Это хорошо:

s = 'A=5'
name, value = s.split('=')
d = {name: int(value)}

Это не:

s = 'A=5'
d = {name: int(value) for name, value in s.split('=')}
ValueError: need more than 1 value to unpack

Почему я не могу распаковать кортеж, когда он находится в понимании словаря? Если я получу эту работу, я могу легко превратить всю функцию в одно компактное понимание dict.


person Benjamin Hodgson♦    schedule 23.08.2012    source источник
comment
Ваш вопрос: почему я не могу распаковать кортеж... поэтому я не буду добавлять к постоянно растущим ответам, которые на самом деле говорят вам, как обойти это, но один простой способ сделать это - использовать конструктор dict с итерируемым аргументом.   -  person Rafael Martins    schedule 15.01.2018


Ответы (7)


В вашем коде s.split('=') вернет список: ['A', '5']. При переборе этого списка каждый раз возвращается одна строка (в первый раз это 'A', во второй раз — '5'), поэтому вы не можете распаковать эту единственную строку в 2 переменные.

Вы можете попробовать: for name,value in [s.split('=')]

Скорее всего, у вас есть итерация строк, которые вы хотите разделить, тогда ваше понимание dict становится простым (2 строки):

 splitstrs = (s.split('=') for s in list_of_strings) 
 d = {name: int(value) for name,value in splitstrs }

Конечно, если вы помешаны на 1-лайнерах, вы можете их комбинировать, но я бы не стал.

person mgilson    schedule 23.08.2012
comment
Если бы я сделал s.split('=') единственным элементом списка, это сработало бы? for name, value in [s.split('=')]? - person Benjamin Hodgson♦; 23.08.2012
comment
Это было не тогда, когда я писал комментарий - person Benjamin Hodgson♦; 23.08.2012
comment
@BigYellowCactus -- Скоро. Я даже не замечал, насколько близко подобрался, пока не посмотрел сегодня утром. (Я больше пытался получить Epic -- но я все еще немного далек от этого). Это немного забавно - может быть, мои ответы становятся лучше, но я в какой-то мере убежден, что люди только начинают голосовать за мои материалы, потому что у меня почти 15 тысяч репутаций... - person mgilson; 23.08.2012
comment
@BigYellowCactus - я знаю, что мои навыки улучшились благодаря тому, что я тусовался на SO. Может быть, когда-нибудь я буду использовать его, чтобы выучить рубин или что-то в этом роде... Я также стремлюсь когда-нибудь получить серебряный (и золотой) значок gnuplot, но, к сожалению, вопросов по этому тегу недостаточно ;). - person mgilson; 23.08.2012
comment
@mgilson Я стараюсь изо всех сил для pygame, но даже бронзовый значок трудно получить :-) - person sloth; 23.08.2012
comment
@BigYellowCactus — второе место за все время — это неплохо (после Алекса — довольно хорошая компания). Вы доберетесь туда (и, вполне вероятно, тоже будете первым, кто получит этот значок). Мне понадобилось довольно много времени, чтобы получить бронзу в gnuplot — пока мне удалось это сделать, но я думаю, что Вольтан когда-нибудь ее получит. Глупо, как усердно мы работаем, чтобы эти маленькие кусочки данных плавали в Интернете ... - person mgilson; 23.08.2012

Конечно, вы могли бы сделать это:

>>> s = 'A=5, b=7'
>>> {k: int(v) for k, v in (item.split('=') for item in s.split(','))}
{'A': 5, ' b': 7}

Но в этом случае я бы просто использовал этот более императивный код:

>>> d = {}
>>> for item in s.split(','):
        k, v = item.split('=')
        d[k] = int(v)


>>> d
{'A': 5, ' b': 7}
person jamylak    schedule 23.08.2012
comment
Я бы определенно предпочел понятную, простую версию функции любой из встроенных версий кода, который, как я ожидал, будет работать дольше, чем один запуск. Это определенно больше созвучно с The Zen of Python. - person John Gaines Jr.; 23.08.2012

Некоторые люди склонны полагать, что вы попадете в ад за использование eval, но...

s = 'A=5, b=7'
eval('dict(%s)' % s)

Или лучше, чтобы быть в безопасности (спасибо mgilson за указание на это):

s = 'A=5, b=7'
eval('dict(%s)' % s, {'__builtins__': None, 'dict': dict})
person sloth    schedule 23.08.2012
comment
Вы могли бы сделать это немного безопаснее: eval('dict(%s)'%s,{'__builtins__':None,'dict':dict}) -- я думаю, что использование этого закроет уязвимости от атак типа инъекции (хотя я хотел бы услышать (желательно дружеский) комментарий, если я ошибаюсь). - person mgilson; 23.08.2012

Как насчет этого кода:

a="A=5, b=9"
b=dict((x, int(y)) for x, y in re.findall("([a-zA-Z]+)=(\d+)", a))
print b

Выход:

{'A': 5, 'b': 9}

Эта версия будет работать и с другими формами ввода, например

a="A=5 b=9 blabla: yyy=100"

дам тебе

{'A': 5, 'b': 9, 'yyy': 100}
person hochl    schedule 23.08.2012
comment
Цифры все еще строки - person jamylak; 23.08.2012
comment
Может быть, это и не нужно, но почему бы не использовать \w+ вместо [a-zA-Z]. Единственная реальная разница в том, что вы начнете подбирать символы подчеркивания (и цифры?) с левой стороны. - person mgilson; 23.08.2012
comment
Конечно, это может варьироваться, это просто пример. Кроме того, _w=5 будет соответствовать, а идентификаторы могут быть просто буквами (из его примера). Если вам нужно, вам придется добавить более сложное выражение для действительных идентификаторов (_w было бы плохо для C/C++ f.e.). - person hochl; 23.08.2012

>>> strs='A=5, b=7'

>>> {x.split('=')[0].strip():int(x.split('=')[1]) for x in strs.split(",")}
{'A': 5, 'b': 7}

для удобочитаемости вы должны использовать обычный цикл for-in вместо понимания.

strs='A=5, b=7'
dic={}
for x in strs.split(','):
  name,val=x.split('=')
  dic[name.strip()]=int(val)
person Ashwini Chaudhary    schedule 23.08.2012
comment
Я думал об этом, но мне никогда не нравились списки индексации напрямую. Мне было интересно, есть ли более читаемый способ сделать это - person Benjamin Hodgson♦; 23.08.2012
comment
Это также разбивает строку в два раза больше, чем необходимо, что немного расточительно, когда единственное, что вы получаете, - это помещать код в 1 (трудночитаемую) строку вместо 4 (легко читаемых) строк... - person mgilson; 23.08.2012

См. ответ мгилсона, почему возникает ошибка. Чтобы достичь того, чего вы хотите, вы можете использовать:

d = {name: int(value) for name,value in (x.split('=',1) for x in s.split(','))}

Чтобы учесть пробелы, используйте .strip() по мере необходимости (например: x.strip().split('=',1)).

person mgibsonbr    schedule 23.08.2012
comment
Я как раз собирался дать это в качестве ответа. :) - person grieve; 23.08.2012
comment
Если вы хотите быть тщательным, вы также можете использовать .split('=',1), просто чтобы убедиться, что он правильно распаковывается. - person mgilson; 23.08.2012
comment
@mgilson, если ввод искажен, тем не менее возникнет исключение ... (т. Е. При вызове int(value) - и это все равно будет ValueError) - person mgibsonbr; 23.08.2012
comment
@mgibsonbr -- Хороший вопрос. Не думал о том, что произойдет после распаковки кортежа. Хотя эта ошибка может быть немного более полезной, чем другие Недостаточно аргументов для распаковки. - person mgilson; 23.08.2012

Как насчет этого?

>>> s
'a=5, b=3, c=4'
>>> {z.split('=')[0].strip(): int(z.split('=')[1]) for z in s.split(',')}
{'a': 5, 'c': 4, 'b': 3}
person Burhan Khalid    schedule 23.08.2012