Devo usar 'has_key ()' ou 'in' nos dicts do Python?

911

Gostaria de saber o que é melhor fazer:

d = {'a': 1, 'b': 2}
'a' in d
True

ou:

d = {'a': 1, 'b': 2}
d.has_key('a')
True
igorgue
fonte

Respostas:

1287

in é definitivamente mais pitônico.

Na verdade has_key()foi removido em Python 3.x .

tonfa
fonte
3
Como um complemento, em Python 3, para verificar a existência de valores, em vez das chaves, tente >>> 1 em d.values ()
riza
217
Uma semi-pegadinha a ser evitada é se certificar de que você faz: "key in some_dict" em vez de "key in some_dict.keys ()". Ambos são equivalentes semanticamente, mas em termos de desempenho, o último é muito mais lento (O (n) vs O (1)). Eu já vi pessoas fazendo o "in dict.keys ()" pensando que é mais explícito e, portanto, melhor.
Adam Parkin
2
@AdamParkin Eu demonstrei seu comentário na minha resposta stackoverflow.com/a/41390975/117471
Bruno Bronosky
8
@AdamParkin No Python 3, keys()é apenas uma visualização de conjunto em um dicionário, em vez de uma cópia, assim x in d.keys()como O (1). Ainda assim, x in dé mais pitônico.
Arthur Tacca
2
@AdamParkin Interessante, eu não vi isso. Suponho que é porque x in d.keys()deve construir e destruir um objeto temporário, completo com a alocação de memória que implica, onde x in d.keys()está apenas fazendo uma operação aritmética (calculando o hash) e fazendo uma pesquisa. Observe que d.keys()é apenas cerca de 10 vezes maior que isso, o que ainda não é muito longo. Eu não verifiquei, mas ainda tenho certeza de que é apenas O (1).
Arthur Tacca
253

in ganha de mãos para baixo, não apenas em elegância (e não sendo depreciada ;-) mas também em desempenho, por exemplo:

$ python -mtimeit -s'd=dict.fromkeys(range(99))' '12 in d'
10000000 loops, best of 3: 0.0983 usec per loop
$ python -mtimeit -s'd=dict.fromkeys(range(99))' 'd.has_key(12)'
1000000 loops, best of 3: 0.21 usec per loop

Embora a seguinte observação nem sempre seja verdadeira, você notará que , geralmente , em Python, a solução mais rápida é mais elegante e pitônica; é por isso que -mtimeité tão útil - não se trata apenas de economizar cem nanossegundos aqui e ali! -)

Alex Martelli
fonte
4
Obrigado por isso, verificando se "in some_dict" é de fato O (1) muito mais fácil (tente aumentar o número 99 para 1999, e você verá que o tempo de execução é o mesmo).
Adam Parkin
2
has_keyparece ser O (1) também.
Dan-gph
96

De acordo com a documentação do python :

has_key()foi descontinuado em favor de key in d.

Nadia Alramli
fonte
1
has_key()foi removido no Python 3
Vadim Kotov
42

Use dict.has_key()se (e somente se) for necessário que seu código seja executável pelas versões do Python anteriores à 2.3 (quando key in dictfoi introduzido).

John Machin
fonte
1
A atualização do WebSphere em 2013 usa o Jython 2.1 como sua principal linguagem de script. Infelizmente, isso ainda é uma coisa útil a ser observada, cinco anos depois que você o anotou.
ArtOfWarfare
23

Há um exemplo em que inrealmente mata seu desempenho.

Se você usar inem um O (1) recipiente que apenas implementos __getitem__e has_key(), mas não __contains__você vai virar um O (1) procurar em uma pesquisa O (N) (como incai de volta para uma busca linear via __getitem__).

A correção é obviamente trivial:

def __contains__(self, x):
    return self.has_key(x)
schlenk
fonte
6
Esta resposta foi aplicável quando foi publicada, mas 99,95% dos leitores podem ignorá-la com segurança. Na maioria dos casos, se você estiver trabalhando com algo tão obscuro, saberá disso.
wizzwizz4
2
Isso realmente não é um problema. has_key()é específico para dicionários Python 2 . in/ __contains__é a API correta a ser usada; para aqueles recipientes onde uma varredura completa é inevitável não há nenhum has_key()método de qualquer maneira , e se há um O (1) abordagem, em seguida, que vai ser de caso de uso específico e assim até o desenvolvedor para escolher o tipo de dados correto para o problema.
Martijn Pieters
15

has_keyé um método de dicionário, mas infuncionará em qualquer coleção e, mesmo quando __contains__estiver ausente, inusará qualquer outro método para iterar a coleção e descobrir.

u0b34a0f6ae
fonte
1
E também funciona nos iteradores "x em xrange (90, 200) <=> 90 <= x <200"
u0b34a0f6ae 28/08/2009
1
...: Isto parece uma idéia muito ruim: 50 operações em vez de 2.
Clément
1
Clément No Python 3, é realmente bastante eficiente fazer intestes em rangeobjetos. Não tenho tanta certeza sobre sua eficiência no Python 2 xrange. ;)
PM 2Ring
@ Clément não está em Python 3; __contains__pode calcular trivialmente se um valor está no intervalo ou não.
Martijn Pieters
1
@AlexandreHuat Seu tempo inclui a sobrecarga de criar uma nova rangeinstância a cada vez. Usando uma instância única e preexistente, o teste "número inteiro no intervalo" é cerca de 40% mais rápido nos meus tempos.
MisterMiyagi
14

A solução para dict.has_key () está obsoleta, use 'in' - sublime text editor 3

Aqui eu peguei um exemplo de dicionário chamado 'idades' -

ages = {}

# Add a couple of names to the dictionary
ages['Sue'] = 23

ages['Peter'] = 19

ages['Andrew'] = 78

ages['Karren'] = 45

# use of 'in' in if condition instead of function_name.has_key(key-name).
if 'Sue' in ages:

    print "Sue is in the dictionary. She is", ages['Sue'], "years old"

else:

    print "Sue is not in the dictionary"
Greena modi
fonte
6
Correto, mas já foi respondido, bem-vindo ao Stackoveflow, obrigado pelo exemplo, verifique sempre as respostas!
igorgue 23/02
@igorgue não tenho certeza sobre os votos negativos para ela. Sua resposta pode ser semelhante à que já foi respondida, mas ela fornece um exemplo. Não é digno o suficiente para ser uma resposta do SO?
Akshat Agarwal
14

Expandindo os testes de desempenho de Alex Martelli com os comentários de Adam Parkin ...

$ python3.5 -mtimeit -s'd=dict.fromkeys(range( 99))' 'd.has_key(12)'
Traceback (most recent call last):
  File "/usr/local/Cellar/python3/3.5.2_3/Frameworks/Python.framework/Versions/3.5/lib/python3.5/timeit.py", line 301, in main
    x = t.timeit(number)
  File "/usr/local/Cellar/python3/3.5.2_3/Frameworks/Python.framework/Versions/3.5/lib/python3.5/timeit.py", line 178, in timeit
    timing = self.inner(it, self.timer)
  File "<timeit-src>", line 6, in inner
    d.has_key(12)
AttributeError: 'dict' object has no attribute 'has_key'

$ python2.7 -mtimeit -s'd=dict.fromkeys(range(  99))' 'd.has_key(12)'
10000000 loops, best of 3: 0.0872 usec per loop

$ python2.7 -mtimeit -s'd=dict.fromkeys(range(1999))' 'd.has_key(12)'
10000000 loops, best of 3: 0.0858 usec per loop

$ python3.5 -mtimeit -s'd=dict.fromkeys(range(  99))' '12 in d'
10000000 loops, best of 3: 0.031 usec per loop

$ python3.5 -mtimeit -s'd=dict.fromkeys(range(1999))' '12 in d'
10000000 loops, best of 3: 0.033 usec per loop

$ python3.5 -mtimeit -s'd=dict.fromkeys(range(  99))' '12 in d.keys()'
10000000 loops, best of 3: 0.115 usec per loop

$ python3.5 -mtimeit -s'd=dict.fromkeys(range(1999))' '12 in d.keys()'
10000000 loops, best of 3: 0.117 usec per loop
Bruno Bronosky
fonte
Estatísticas maravilhosas, às vezes implícitas, podem ser melhores do que explícitas (pelo menos em termos de eficiência) ... #
3100
Obrigado, @varun. Eu tinha esquecido essa resposta. Eu preciso fazer esse tipo de teste com mais frequência. Eu leio regularmente tópicos longos em que as pessoas discutem sobre a melhor maneira de fazer as coisas. Mas raramente me lembro de como isso foi fácil de obter provas .
de Bruno Bronosky
0

Se você tem algo parecido com isto:

t.has_key(ew)

altere para abaixo para rodar no Python 3.X e acima:

key = ew
if key not in t
Harshita Jhavar
fonte
6
Não, você inverteu o teste. t.has_key(ew)retorna Truese as ewreferências de valor também são uma chave no dicionário. key not in tretorna Truese o valor for nãono dicionário. Além disso, okey = ewalias é muito, muito redundante. A ortografia correta éif ew in t. Qual é o que a resposta aceita de 8 anos antes já lhe disse.
Martijn Pieters