Verifique se algo está (não) em uma lista em Python

314

Eu tenho uma lista de tuplas em Python e tenho uma condicional na qual quero assumir o ramo SOMENTE se a tupla não estiver na lista (se estiver na lista, não quero usar o ramo if)

if curr_x -1 > 0 and (curr_x-1 , curr_y) not in myList: 

    # Do Something

Isso realmente não está funcionando para mim. O que eu fiz errado?

Zack
fonte
1
Note-se que 3 -1 > 0 and (4-1 , 5) not in []True, portanto, o erro não é um dos precedência do operador.
Dan D.
6
O que você quer dizer com "realmente não está trabalhando para mim"? O que você espera que aconteça? O que realmente acontece? Que conteúdo exato da lista desencadeia o problema?
Karl Knechtel
Por que não tentar myList.count((curr_x, curr_y)), se (curr_x, curr_y)não está na myList, o resultado será0
LittleLittleQ
2
Como essa pergunta "meu código não está realmente funcionando para mim" recebeu 297 votos positivos? Por favor, dê-nos um exemplo reprodutível mínimo .
gerrit 17/04

Respostas:

503

O bug provavelmente está em outro lugar no seu código, porque deve funcionar bem:

>>> 3 not in [2, 3, 4]
False
>>> 3 not in [4, 5, 6]
True

Ou com tuplas:

>>> (2, 3) not in [(2, 3), (5, 6), (9, 1)]
False
>>> (2, 3) not in [(2, 7), (7, 3), "hi"]
True
orlp
fonte
11
@Zack: se você não sabia sobre isso, você poderia apenas fazerif not ELEMENT in COLLECTION:
ninjagecko
@ ninjagecko: dependendo do tipo de contêiner que poderia ser menos eficiente ou até incorreto. Veja, por exemplo, filtros de bloom .
orlp
14
@ nightcracker Isso não faz sentido, pois A not in Bé reduzido a fazer o not B.__contains__(A)que é o mesmo que o que not A in Bé reduzido a qual é not B.__contains__(A).
Dan D.
1
Oh uau, eu poderia jurar que Python tinha algo parecido __notcontains__. Sinto muito, então o que eu disse é apenas besteira.
orlp
2
@ std''OrgnlDave A única maneira de isso acontecer é se nottiver maior precedência do inque a que não tem. Considere o resultado do ast.dump(ast.parse("not A in B").body[0])qual resulta em "Expr(value=UnaryOp(op=Not(), operand=Compare(left=Name(id='A', ctx=Load()), ops=[In()], comparators=[Name(id='B', ctx=Load())])))"Se notagrupado firmemente em A, seria de esperar que o resultado fosse "Expr(value=Compare(left=UnaryOp(op=Not(), operand=Name(id='A', ctx=Load())), ops=[In()], comparators=[Name(id='B', ctx=Load())]))"qual é a análise "(not A) in B".
Dan D.
20

Como verifico se algo está (não) em uma lista no Python?

A solução mais barata e legível é usar o inoperador (ou no seu caso específico not in). Conforme mencionado na documentação,

Os operadores ine not inteste para associação. x in savalia Truese xé um membro se de Falseoutra forma. x not in sretorna a negação de x in s.

Além disso,

O operador not inestá definido para ter o valor verdadeiro inverso de in.

y not in xé logicamente o mesmo que not y in x.

Aqui estão alguns exemplos:

'a' in [1, 2, 3]
# False

'c' in ['a', 'b', 'c']
# True

'a' not in [1, 2, 3]
# True

'c' not in ['a', 'b', 'c']
# False

Isso também funciona com tuplas, uma vez que as tuplas são hashable (como conseqüência do fato de serem imutáveis):

(1, 2) in [(3, 4), (1, 2)]
#  True

Se o objeto no RHS definir um __contains__()método, ele inserá chamado internamente, conforme observado no último parágrafo da seção Comparações dos documentos.

... ine not insão suportados por tipos que são iteráveis ​​ou implementam o __contains__()método. Por exemplo, você poderia (mas não deveria) fazer isso:

[3, 2, 1].__contains__(1)
# True

incurto-circuito, portanto, se o seu elemento estiver no início da lista, inavalie mais rapidamente:

lst = list(range(10001))
%timeit 1 in lst
%timeit 10000 in lst  # Expected to take longer time.

68.9 ns ± 0.613 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
178 µs ± 5.01 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

Se você deseja fazer mais do que apenas verificar se um item está em uma lista, existem opções:

  • list.indexpode ser usado para recuperar o índice de um item. Se esse elemento não existir, a ValueErroré gerado.
  • list.count pode ser usado se você quiser contar as ocorrências.

O problema XY: você já considerou sets?

Faça a si mesmo estas perguntas:

  • você precisa verificar se um item está em uma lista mais de uma vez?
  • Essa verificação é feita dentro de um loop ou uma função é chamada repetidamente?
  • Os itens que você está armazenando na sua lista são laváveis? IOW, você pode chamá hash-los?

Se você respondeu "sim" a essas perguntas, deve usar um set. Um inteste de associação em lists é O (n) complexidade de tempo. Isso significa que o python precisa fazer uma varredura linear da sua lista, visitando cada elemento e comparando-o com o item de pesquisa. Se você estiver fazendo isso repetidamente, ou se as listas forem grandes, essa operação acarretará uma sobrecarga.

setobjetos, por outro lado, hash seus valores para verificação de associação em tempo constante. A verificação também é feita usando in:

1 in {1, 2, 3} 
# True

'a' not in {'a', 'b', 'c'}
# False

(1, 2) in {('a', 'c'), (1, 2)}
# True

Se você é infeliz o suficiente para que o elemento que você está procurando / não esteja no final da sua lista, o python terá escaneado a lista até o final. Isso é evidente nos horários abaixo:

l = list(range(100001))
s = set(l)

%timeit 100000 in l
%timeit 100000 in s

2.58 ms ± 58.9 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
101 ns ± 9.53 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

Lembre-se de que essa é uma opção adequada, desde que os elementos que você está armazenando e procurando sejam laváveis. IOW, eles teriam que ser tipos imutáveis ​​ou objetos implementados __hash__.

cs95
fonte
2
Conjuntos nem sempre são uma opção (por exemplo, ao ter uma lista de itens mutáveis). Para coleções grandes: a criação do conjunto para uma pesquisa é de O (n) tempo de qualquer maneira e pode estar dobrando o uso de memória. Se você ainda não tem uma pesquisa, nem sempre é a melhor opção para fazer / manter uma.
wim