Por que o uso de len (SEQUENCE) em valores de condição é considerado incorreto pelo Pylint?

211

Considerando este trecho de código:

from os import walk

files = []
for (dirpath, _, filenames) in walk(mydir):
    # more code that modifies files
if len(files) == 0: # <-- C1801
    return None

Fiquei alarmado com Pylint com esta mensagem sobre a linha com a instrução if:

[pylint] C1801: Não use len(SEQUENCE)como valor de condição

A regra C1801, à primeira vista, não me pareceu muito razoável, e a definição no guia de referência não explica por que isso é um problema. De fato, ele chama de uso incorreto .

len como condição (C1801) : não use len(SEQUENCE)como valor de condição Usado quando o Pylint detecta o uso incorreto de len (sequência) dentro das condições.

Minhas tentativas de pesquisa também falharam em me fornecer uma explicação mais profunda. Entendo que a propriedade de comprimento de uma sequência pode ser avaliada preguiçosamente e que __len__pode ser programada para ter efeitos colaterais, mas é questionável se isso é problemático o suficiente para que Pylint chame esse uso de incorreto. Portanto, antes de simplesmente configurar meu projeto para ignorar a regra, gostaria de saber se estou perdendo alguma coisa no meu raciocínio.

Quando é len(SEQ)problemático o uso como valor de condição? Que situações principais a Pylint está tentando evitar com o C1801?

E_net4 é um voto negativo
fonte
9
Porque você pode avaliar a veracidade da sequência diretamente. pylint quer que você faça if files:ouif not files:
Patrick Haugh
38
lennão conhece o contexto em que é chamado; portanto, se calcular o comprimento significa atravessar a sequência inteira, ele deve; ele não sabe que o resultado está sendo comparado a 0. A computação do valor booleano pode parar depois que ele vê o primeiro elemento, independentemente de quanto tempo a sequência realmente é. Eu acho que o pylint está sendo um pouco opinativo aqui; Não consigo pensar em nenhuma situação em que seja errado usar len, apenas que é uma opção pior que a alternativa.
chepner
2
@ E_net4 Acho que o PEP-8 é provavelmente o lugar para começar.
Patrick Haugh 30/03
6
SEQUÊNCIAS precisam de um 'empty ()' ou 'isempty ()' como C ++ imo.
JDonner

Respostas:

281

Quando é len(SEQ)problemático o uso como valor de condição? Que situações principais a Pylint está tentando evitar com o C1801?

Não é realmente problemático de usar len(SEQUENCE)- embora possa não ser tão eficiente (veja o comentário de chepner ). Independentemente disso, a Pylint verifica o código quanto à conformidade com o guia de estilo do PEP 8, que afirma que

Para seqüências (strings, listas, tuplas), use o fato de que sequências vazias são falsas.

Yes: if not seq:
     if seq:

No:  if len(seq):
     if not len(seq):

Como programador ocasional em Python, que alterna entre linguagens, consideraria a len(SEQUENCE)construção mais legível e explícita (“Explícito é melhor que implícito”). No entanto, o uso do fato de que uma sequência vazia é avaliada Falseem um contexto booleano é considerado mais "pitônico".

Anthony Geoghegan
fonte
Como fazer isso funcionar:if len(fnmatch.filter(os.listdir(os.getcwd()), 'f_*')):
Marichyasana 3/17/17
@ Marichyasana Acho que coisas assim podem (teoricamente) ser escritas como if next(iter(...), None) is not None:(se a sequência não puder conter None). Isso é longo, mas len(fnmatch...)também é longo; ambos precisam ser divididos.
precisa saber é o seguinte
13
Eu também sou um usuário ocasional de Python e muitas vezes tenho a impressão de que o "caminho Pythonic" ficou meio confuso em sua própria ambiguidade.
luqo33
3
Apenas uma pergunta geral, essas recomendações do PEP podem ser revisadas? Outra razão pela qual o len(s) == 0é superior na minha opinião é que ele é generalizável para outros tipos de sequências. Por exemplo, pandas.Seriese matrizes numpy. if not s:não está por outro lado e, nesse caso, você precisaria usar uma avaliação separada para todos os tipos possíveis de objetos semelhantes a matrizes (ou seja pd.DataFrame.empty).
Marses
2
A propósito, nenhuma of collections.abcclasse declara o __bool__método. Em outras palavras, como posso ter certeza de que posso usar bool(seq)se souber que é um collections.abc.Collection? Além disso, algumas bibliotecas declaram que é proibido verificar bool(collection)suas classes.
Eir Nym
42

Observe que o uso de len (seq) é de fato necessário (em vez de apenas verificar o valor bool de seq) ao usar matrizes NumPy.

a = numpy.array(range(10))
if a:
    print "a is not empty"

resulta em uma exceção: ValueError: o valor verdadeiro de uma matriz com mais de um elemento é ambíguo. Use a.any () ou a.all ()

E, portanto, para o código que usa listas Python e matrizes NumPy, a mensagem C1801 é menos que útil.

Cameron Hayne
fonte
5
Eu concordo com a sua declaração. Com a edição nº 1405 agora levantada, espero ver o C1801 reformado para algo útil ou desativado por padrão.
E_net4 é um voto negativo
2
além disso, é inútil verificar se uma sequência possui um determinado número de elementos. Só é bom verificar se está completamente vazio no melhor dos casos.
PabTorre
1

Esse foi um problema no pylint e não é mais considerado len(x) == 0incorreto.

Você não deve usar o bare len(x) como condição. A comparação len(x)com um valor explícito, como if len(x) == 0de, if len(x) > 0é totalmente adequada e não é proibida pelo PEP 8.

Do PEP 8 :

# Correct:
if not seq:
if seq:

# Wrong:
if len(seq):
if not len(seq):

Observe que testar explicitamente o comprimento não é proibido. O Zen do Python afirma:

Explícito é melhor que implícito.

Na escolha entre if not seqe if not len(seq), ambos estão implícitos, mas o comportamento é diferente. Mas if len(seq) == 0ou if len(seq) > 0são comparações explícitas e, em muitos contextos, o comportamento correto.

Em pylint, o PR 2815 corrigiu esse bug, relatado pela primeira vez como problema 2684 . Ele continuará reclamando if len(seq), mas não reclamará mais if len(seq) > 0. O PR foi mesclado 19/03/2019, portanto, se você estiver usando o pylint 2.4 (lançado em 2019-09-14), não deverá encontrar esse problema.

gerrit
fonte
0

Pylint estava falhando no meu código e a pesquisa me levou a este post:

../filename.py:49:11: C1801: Do not use `len(SEQUENCE)` to determine if a sequence is empty (len-as-condition)
../filename.py:49:34: C1801: Do not use `len(SEQUENCE)` to determine if a sequence is empty (len-as-condition)

Este foi o meu código antes:

def list_empty_folders(directory):
"""The Module Has Been Build to list empty Mac Folders."""
for (fullpath, dirnames, filenames) in os.walk(directory):
    if len(dirnames) == 0 and len(filenames) == 0:
        print("Exists: {} : Absolute Path: {}".format(
            os.path.exists(fullpath), os.path.abspath(fullpath)))

Isso foi depois da minha correção de código. Ao usar o int() attribute, eu pareço ter satisfeito o Pep8 / Pylint e não parece ter um impacto negativo no meu código:

def list_empty_folders(directory):
"""The Module Has Been Build to list empty Mac Folders."""
for (fullpath, dirnames, filenames) in os.walk(directory):
    if len(dirnames).__trunc__() == 0 and len(filenames).__trunc__() == 0:
        print("Exists: {} : Absolute Path: {}".format(
            os.path.exists(fullpath), os.path.abspath(fullpath)))

My Fix

Ao adicionar .__trunc__()à sequência, parece ter resolvido a necessidade.

Não vejo diferença no comportamento, mas se alguém souber de detalhes que estão faltando, entre em contato.

JayRizzo
fonte
1
Você está chamando __trunc__()a saída de len(seq), que (de certa forma redundante) trunca o valor do comprimento para um número inteiro. Ele apenas "finge" o fiapo sem abordar o motivo por trás dele. A sugestão na resposta aceita não funcionou para você?
E_net4 é um voto negativo 03/04
Não nas minhas tentativas. Entendo a redundância, mas mesmo depois que esse problema foi solucionado pelos desenvolvedores em github.com/PyCQA/pylint/issues/1405 e 2684 e foi incorporado, no meu entendimento, isso não deve ser um problema ao executar o pylint, mas Ainda vejo esse problema mesmo depois de atualizar meu pylint. Eu só queria compartilhar, como this worked for me, mesmo que não seja totalmente apropriado. Mas, para esclarecer, mesmo que seja redundante, se você estiver fazendo uma comparação len (seq) == 0, o trunc não deve ter que fazer nada, pois já são números inteiros. certo?
JayRizzo
1
Exatamente, ele já é um número inteiro e __trunc__()não faz nada significativo. Observe que não me referi à comparação como sendo redundante, mas a esta tentativa de truncar o comprimento. O aviso desaparece apenas porque espera apenas uma expressão do formulário len(seq) == 0. Acredito que o fiapo nesse caso esperaria que você substituísse a instrução if pela seguinte:if not dirnames and not filenames:
E_net4 é um downvote
Testar a veracidade tem as conseqüências não intencionais de ser "sempre verdadeiro" se a __bool__função não estiver definida na sequência subjacente.
Erik Aronesty