filtrar itens em um dicionário python onde as chaves contêm uma string específica

95

Sou um programador C desenvolvendo algo em python. Eu sei como fazer o seguinte em C (e, portanto, em lógica semelhante a C aplicada a python), mas estou me perguntando qual é a maneira 'Python' de fazer isso.

Eu tenho um dicionário d, e gostaria de operar em um subconjunto de itens, apenas aqueles cuja chave (string) contém uma substring específica.

ou seja, a lógica C seria:

for key in d:
    if filter_string in key:
        # do something
    else
        # do nothing, continue

Estou imaginando que a versão python seria algo como

filtered_dict = crazy_python_syntax(d, substring)
for key,value in filtered_dict.iteritems():
    # do something

Eu encontrei muitos posts aqui sobre filtragem de dicionários, mas não consegui encontrar um que envolvesse exatamente isso.

Meu dicionário não está aninhado e estou usando o python 2.7

memorando
fonte

Respostas:

182

Que tal uma compreensão de dicionário :

filtered_dict = {k:v for k,v in d.iteritems() if filter_string in k}

Se você ver, deve ser autoexplicativo, já que se parece muito bem com o inglês.

Esta sintaxe requer Python 2.7 ou superior.

No Python 3, existe apenas dict.items(), não iteritems()então você usaria:

filtered_dict = {k:v for (k,v) in d.items() if filter_string in k}
Jonathon Reinhart
fonte
1
Porque não filtered_dict = {k:d[k] for k in d if filter_string in k}?
thefourtheye
5
@thefourtheye Vou adivinhar que o meu é mais rápido, pois não incorre na d[k]pesquisa.
Jonathon Reinhart
Além disso, ele diz # do somethingnos comentários, mas deixamos cair algumas chaves aqui.
thefourtheye
Temos iteritemsem Python 3? Acho que não. Então, minha versão seria compatível, não?
thefourtheye
1
No Python 3 você substituiria iteritemspor items, que é igual ao do Python 2.7 iteritems.
Jonathon Reinhart
17

Escolha o que for mais legível e de fácil manutenção. Só porque você pode escrever em uma única linha, não significa que você deve. Sua solução existente é próxima do que eu usaria, exceto iteritems de usuário para pular a pesquisa de valor, e odeio ifs aninhados se eu puder evitá-los:

for key, val in d.iteritems():
    if filter_string not in key:
        continue
    # do something

No entanto, se você realmente deseja algo que permita a iteração por meio de um dicionário filtrado, então eu não faria o processo de duas etapas de construção do dicionário filtrado e, em seguida, iterá-lo, mas em vez disso, usaria um gerador, porque o que é mais pythônico (e incrível) do que um gerador?

Primeiro, criamos nosso gerador, e um bom design exige que o tornemos abstrato o suficiente para ser reutilizável:

# The implementation of my generator may look vaguely familiar, no?
def filter_dict(d, filter_string):
    for key, val in d.iteritems():
        if filter_string not in key:
            continue
        yield key, val

E então podemos usar o gerador para resolver seu problema de forma limpa e simples com um código simples e compreensível:

for key, val in filter_dict(d, some_string):
    # do something

Resumindo: os geradores são fantásticos.

Brendan F
fonte
11

Você pode usar a função de filtro embutida para filtrar dicionários, listas, etc. com base em condições específicas.

filtered_dict = dict(filter(lambda item: filter_str in item[0], d.items()))

A vantagem é que você pode usá-lo para diferentes estruturas de dados.

Pulkit
fonte
Observe que items:deve estar item:na definição lambda.
bkribbs
Obrigado @bkribbs por apontar o erro. Eu retifiquei isso agora.
Pulkit
8
input = {"A":"a", "B":"b", "C":"c"}
output = {k:v for (k,v) in input.items() if key_satifies_condition(k)}
jspurim
fonte
3
Meu método usando iteritems()vai ser mais eficiente do que items().
Jonathon Reinhart
@Jonathin Reinhart Eu não sabia disso. Obrigado.
jspurim
2
Apenas no Python 2.7. No Python 3 existe apenas items() , que atua como o do Python 2.7 iteritems.
Jonathon Reinhart
1
A pergunta é explicitamente para python 2.7
Brendan F
7

Jonathon deu a você uma abordagem usando compreensões de dicionário em sua resposta . Aqui está uma abordagem que lida com sua parte de fazer algo .

Se você quiser fazer algo com os valores do dicionário, não precisa de uma compreensão de dicionário:

Estou usando iteritems() já que você marcou sua pergunta com

results = map(some_function, [(k,v) for k,v in a_dict.iteritems() if 'foo' in k])

Agora o resultado estará em uma lista com some_functionaplicada a cada par chave / valor do dicionário, que possui fooem sua chave.

Se você deseja apenas lidar com os valores e ignorar as chaves, basta alterar a compreensão da lista:

results = map(some_function, [v for k,v in a_dict.iteritems() if 'foo' in k])

some_function pode ser qualquer chamável, então um lambda também funcionaria:

results = map(lambda x: x*2, [v for k,v in a_dict.iteritems() if 'foo' in k])

A lista interna não é necessária, pois você também pode passar uma expressão geradora para mapear:

>>> map(lambda a: a[0]*a[1], ((k,v) for k,v in {2:2, 3:2}.iteritems() if k == 2))
[4]
Burhan Khalid
fonte
interessante. como seria definida alguma função? no primeiro caso (k, v), são necessários apenas dois parâmetros? primeira chave e depois valor?
memorando de
Sim, apenas um cobrável. Então map(lambda a: a[0]*a[1], ((k,v) for k,v in {2:2, 3:2}.iteritems() if k == 2))- isso vai dar a você [4].
Burhan Khalid
Isso é correto, mas mais pitônico do que usar mapé uma compreensão de lista. [f(v) for k, v in d.iteritems() if substring in k]Acho que é muito mais legível e mais eficiente.
Davidmh
@memo Não precisaria de dois parâmetros, mas de um único parâmetro com dois elementos. Também existe um mapa estelar que será descompactado em dois argumentos, no entanto, é um iterador lento (deve ser iterado antes de ser executado, results = list(starmap(...))ou seja, ou for result in starmap(...): ...).
nmclean