Mapeando sobre valores em um dicionário python

243

Dado um dicionário, { k1: v1, k2: v2 ... }eu quero obter { k1: f(v1), k2: f(v2) ... }desde que eu passe uma função f.

Existe alguma função incorporada? Ou eu tenho que fazer

dict([(k, f(v)) for (k, v) in my_dictionary.iteritems()])

Idealmente, eu apenas escreveria

my_dictionary.map_values(f)

ou

my_dictionary.mutate_values_with(f)

Ou seja, não me interessa se o dicionário original está mutado ou se uma cópia é criada.

Tarrasch
fonte
2
Uma maneira melhor de escrever seu exemplo seria dict((k, f(v)) for k, v in mydict.iteritems()), ou seja, sem colchetes, que impediria a criação de uma lista intermediária por meio de um gerador.
bereal 01/09/12

Respostas:

354

Não existe essa função; a maneira mais fácil de fazer isso é usar uma compreensão de ditado:

my_dictionary = {k: f(v) for k, v in my_dictionary.items()}

No python 2.7, use o .iteritems()método em vez de .items()economizar memória. A sintaxe de compreensão de ditados não foi introduzida até o python 2.7.

Observe que também não existe esse método nas listas; você precisaria usar uma compreensão de lista ou a map()função.

Como tal, você também pode usar a map()função para processar seu dict:

my_dictionary = dict(map(lambda kv: (kv[0], f(kv[1])), my_dictionary.iteritems()))

mas isso não é legível, na verdade.

Martijn Pieters
fonte
5
+1: é isso que eu faria também. dict(zip(a, map(f, a.values())))é marginalmente mais curto, mas tenho que pensar no que está fazendo e me lembrar que sim, chaves e valores são iterados na mesma ordem, se o ditado não mudar. Não preciso pensar no que o dictcomp está fazendo, e por isso é a resposta certa.
DSM
2
@chiborg: isso porque, em vez de procurar todos os pares de valores-chave de uma só vez, agora você está usando o número de chaves vezes my_dictionary.__getitem__.
Martijn Pieters
1
Note-se que desde PEP3113 (implementado em 3.x python) parâmetros tupla não são mais suportadas: lambda (k,v): (k, f(v))deve ser reescrito para algo comolambda k_v: (k_v[0], f(k_v[1]))
normanius
1
Por que a descompactação de parâmetros foi nixada? Como isso é uma melhoria ?
Javadba
3
vindo de uma linguagem FP, Python pareceria incrivelmente estranho.
Juanchito
21

Você pode fazer isso no local, em vez de criar um novo ditado, que pode ser preferível para dicionários grandes (se você não precisar de uma cópia).

def mutate_dict(f,d):
    for k, v in d.iteritems():
        d[k] = f(v)

my_dictionary = {'a':1, 'b':2}
mutate_dict(lambda x: x+1, my_dictionary)

resulta em my_dictionaryconter:

{'a': 2, 'b': 3}
gens
fonte
1
Cool, talvez você deve renomear mapdicta mutate_values_withou algo para deixar bem claro que você reescrever o dict! :)
Tarrasch
2
zip(d.keys(), d.values())obras para mais versões em vez deiteritems()
ytpillai
1
@ytpillai 'zip' ou compreensões fazem uma cópia, em vez de alterar os valores no local, que é o objetivo da minha resposta. A resposta aceita é a melhor para quando uma cópia está correta.
gens
1
Minhas desculpas, eu não sabia que você queria usar o método de itens. No entanto, outra melhoria para o que é possível, bem como (para usuários não Python 2.7){k:f(v) for k,v in iter(d.items())}
ytpillai
1
Economiza espaço, fazendo uma iteração
ytpillai
13

Devido ao PEP-0469, que renomeou iteritems () para items (), e PEP-3113, que removeu a descompactação do parâmetro Tuple , no Python 3.x, você deve escrever Martijn Pieters .

my_dictionary = dict(map(lambda item: (item[0], f(item[1])), my_dictionary.items()))
lucidyan
fonte
4

Embora minha resposta original tenha falhado o ponto (tentando resolver esse problema com a solução para acessar a chave na fábrica do padrão ), eu a refiz para propor uma solução real para a presente pergunta.

Aqui está:

class walkableDict(dict):
  def walk(self, callback):
    try:
      for key in self:
        self[key] = callback(self[key])
    except TypeError:
      return False
    return True

Uso:

>>> d = walkableDict({ k1: v1, k2: v2 ... })
>>> d.walk(f)

A idéia é subclassificar o ditado original para fornecer a funcionalidade desejada: "mapear" uma função sobre todos os valores.

O ponto positivo é que esse dicionário pode ser usado para armazenar os dados originais como se fossem dict, enquanto transforma qualquer dado sob solicitação com um retorno de chamada.

Obviamente, sinta-se à vontade para nomear a classe e a função da maneira que desejar (o nome escolhido nesta resposta é inspirado na array_walk()função do PHP ).

Nota: Nem o bloco try- exceptnem as returninstruções são obrigatórias para a funcionalidade, elas existem para imitar ainda mais o comportamento do PHP array_walk.

7heo.tk
fonte
1
Isso não resolve a questão do OP, pois o __missing__método não será chamado para chaves existentes, que queremos transformar, a menos que o método de fábrica passado use o dict de origem como um fallback de alguma forma, mas como isso não faz parte do exemplo de uso, Considero isso uma resposta insatisfatória para o problema em questão.
Kaos
Quais chaves existentes?
7heo.tk
A partir do OP: Given a dictionary { k1: v1, k2: v2 ... } .... Ou seja, você já tem dictque começar com .. #
271 Kaos
Eu gostaria de dizer que nós dois estamos certos; mas acredito que ambos estamos errados. Você está certo em que minha resposta não responde à pergunta; mas não pelo motivo que você chamou. Eu simplesmente errei o argumento, dando uma maneira de obter {v1: f(v1), v2: f(v2), ...}dados [v1, v2, ...], e não dados. Vou editar minha resposta para corrigir isso.
7heo.tk
2

Para evitar a indexação de dentro do lambda, como:

rval = dict(map(lambda kv : (kv[0], ' '.join(kv[1])), rval.iteritems()))

Você também pode fazer:

rval = dict(map(lambda(k,v) : (k, ' '.join(v)), rval.iteritems()))
sinceramente
fonte
Essa é uma manipulação inteligente dentro da própria tupla 2 no segundo exemplo. No entanto, ele utiliza descompactação automática de tupla no lambda, que não é mais suportado no Python 3. Portanto lambda(k,v), não funcionará. Veja stackoverflow.com/questions/21892989/…
Jonathan Komar
0

Apenas veio através deste caso de uso. Eu implementei a resposta de gens , adicionando uma abordagem recursiva para lidar com valores que também são ditados:

def mutate_dict_in_place(f, d):
    for k, v in d.iteritems():
        if isinstance(v, dict):
            mutate_dict_in_place(f, v)
        else:
            d[k] = f(v)

# Exemple handy usage
def utf8_everywhere(d):
    mutate_dict_in_place((
        lambda value:
            value.decode('utf-8')
            if isinstance(value, bytes)
            else value
        ),
        d
    )

my_dict = {'a': b'byte1', 'b': {'c': b'byte2', 'd': b'byte3'}}
utf8_everywhere(my_dict)
print(my_dict)

Isso pode ser útil ao lidar com arquivos json ou yaml que codificam cadeias de caracteres como bytes no Python 2

Oyono
fonte