O que significa a mensagem "Muito poucos métodos públicos" do pylint

110

Estou executando o pylint em algum código e recebendo o erro "Muito poucos métodos públicos (0/2)". O que esta mensagem significa? Os documentos do pylint não são úteis:

Usado quando a classe tem poucos métodos públicos, portanto, certifique-se de que realmente vale a pena.

monsur
fonte
1
Como é a sua aula? A classe faz alguma coisa além de armazenar dados?
Blender
1
Tudo o que a classe faz é armazenar dados.
Monsur
2
Bem, aí está o seu problema. As aulas não são destinadas a armazenar dados. É para isso que servem as estruturas de dados, como dicionários e listas.
Blender
Interessante, obrigado! A mensagem de erro do pylint pode ser mais útil. De qualquer forma, fique à vontade para transformar seu comentário em uma resposta e eu aprovarei.
Monsur
6
Mas onde está a definição de "poucos"? Eu tenho exatamente um método. Essa é a razão pela qual a classe existe. Como o pylint define "poucos"? Mais de 2? Por quê?
Zordid

Respostas:

124

O erro basicamente diz que as classes não servem apenas para armazenar dados, pois você basicamente trata a classe como um dicionário. As classes devem ter pelo menos alguns métodos para operar nos dados que contêm.

Se sua classe é assim:

class MyClass(object):
    def __init__(self, foo, bar):
        self.foo = foo
        self.bar = bar

Considere usar um dicionário ou um namedtuple. Embora se uma classe parecer a melhor escolha, use-a. pylint nem sempre sabe o que é melhor.

Observe que namedtupleé imutável e os valores atribuídos na instanciação não podem ser modificados posteriormente.

Liquidificador
fonte
72
+1 para "pylint não sabe o que é melhor" - use seu próprio julgamento, mas como regra, se o que você precisa é uma "estrutura", use um dictou namedtuple. Use uma classe quando quiser adicionar alguma lógica ao seu objeto (por exemplo, você deseja que as coisas aconteçam quando ele é criado, você precisa que algumas coisas especiais aconteçam quando for adicionado, você deseja realizar algumas operações nele, controlar como é exibido, etc.)
Burhan Khalid
Obrigado pelas respostas detalhadas! Meu caso de uso é semelhante ao que Burhan mencionou, estou fazendo algum processamento nos dados quando são criados.
Monsur
6
Este erro não faz sentido se você tiver Meta (metaclasse) dentro de sua definição de classe.
alexander_ch
11
namedtupleé uma merda - além de ter uma sintaxe feia, você não pode documentá-la ou fornecer valores padrão facilmente.
rr-
6
Sempre que usei namedtuple, me arrependi da decisão. É inconsistente permitir acesso nomeado e atributos de acesso indexados.
teoria
39

Se você está estendendo uma aula, então minha sugestão é desabilitar sistematicamente este aviso e seguir em frente, por exemplo, no caso de tarefas de aipo:

class MyTask(celery.Task):  # pylint: disable=too-few-public-methods                                                                                   
    """base for My Celery tasks with common behaviors; extends celery.Task

    ...             

Mesmo se você estiver apenas estendendo uma única função, você definitivamente precisa de uma classe para fazer essa técnica funcionar, e estender é definitivamente melhor do que hackear as classes de terceiros!

sábio
fonte
Tendo este diálogo, o pré-commit agora me dá: Valor de opção ruim 'método público-insuficiente' (valor de opção ruim)
Mercúrio
Você incluiu o 's' nos métodos? Sua mensagem de valor de opção ruim não tem.
Sábado
4
Provavelmente, a melhor maneira de desabilitar isso é definindo min-public-methods=0na [BASIC]seção do arquivo de configuração. Isso permite que você coloque em uma linha separada de todas as suas disable=coisas (in [MESSAGE CONTROL]), o que torna mais fácil adicionar comentários detalhados sobre por que você habilitou e desabilitou coisas junto com a mudança de configuração.
cjs
15

Este é outro caso de pylintregras cegas de.

"As classes não têm como objetivo armazenar dados" - esta é uma afirmação falsa. Os dicionários não são bons para tudo. Um membro de dados de uma classe é algo significativo, um item de dicionário é algo opcional. Prova: você pode fazer dictionary.get('key', DEFAULT_VALUE)para prevenir um KeyError, mas não é simples __getattr__com o padrão.

EDITAR - maneiras recomendadas de usar estruturas

Eu preciso atualizar minha resposta. Agora - se você precisar de umstruct , você tem duas ótimas opções:

a) Basta usar attrs

Esta é uma biblioteca para isso:

https://www.attrs.org/en/stable/

import attr

@attr.s
class MyClass(object):  # or just MyClass: for Python 3
    foo = attr.ib()
    bar = attr.ib()

O que você ganha extra: não escrever construtores, valores padrão, validação, __repr__objetos somente leitura (para substituir namedtuples, mesmo no Python 2) e muito mais.

b) Use dataclasses(Py 3.7+)

Após o comentário de hwjp, também recomendo dataclasses:

https://docs.python.org/3/library/dataclasses.html

Isso é quase tão bom quanto attrs, e é um mecanismo de biblioteca padrão ("baterias incluídas"), sem dependências extras, exceto Python 3.7+.

Resto da resposta anterior

NamedTuplenão é ótimo - especialmente antes do python 3 typing.NamedTuple: https://docs.python.org/3/library/typing.html#typing.NamedTuple - você definitivamente deve verificar o NamedTuplepadrão "classe derivada de ". Python 2 - namedtuplescriado a partir de descrições de string - é feio, ruim e "programar dentro de literais de string" é estúpido.

Concordo com as duas respostas atuais ("considere usar outra coisa, mas o pylint nem sempre está certo" - a aceita e "use o comentário de supressão do pylint"), mas tenho minha própria sugestão.

Deixe-me apontar isso mais uma vez: algumas classes destinam-se apenas a armazenar dados.

Agora, a opção de considerar também - use property-ies.

class MyClass(object):
    def __init__(self, foo, bar):
        self._foo = foo
        self._bar = bar

    @property
    def foo(self):
        return self._foo

    @property
    def bar(self):
        return self._bar

Acima você tem propriedades somente leitura, o que é OK para Value Object (por exemplo, como aquelas em Domain Driven Design), mas você também pode fornecer setters - desta forma, sua classe será capaz de assumir a responsabilidade pelos campos que você possui - por exemplo para fazer alguma validação etc. (se você tiver setters, você pode atribuir usando-os no construtor, ou seja, em self.foo = foovez de direto self._foo = foo, mas cuidado, os setters podem assumir que outros campos já foram inicializados, e então você precisa de validação personalizada no construtor) .

Tomasz Gandor
fonte
2
No Python 3.7 e superior, as classes de dados fornecem uma boa solução, abordando algumas das feiúras das duplicatas nomeadas e são perfeitas para objetos de valor DDD.
hwjp
Eu concordo, e de 2020 em diante é o caminho padrão a seguir. Para ter um mecanismo de ampla gama de versões (2.7, 3.3+, se bem me lembro), você poderia usar a attrsbiblioteca, que era na verdade o projeto para a criação do dataclassesmódulo.
Tomasz Gandor
namedtuplestem uma sintaxe estranha para herança ... exigindo que cada classe usando uma saiba que é uma tupla nomeada e use em __new__vez de __init__. dataclassesnão tem essa limitação
Erik Aronesty
4

É difícil quando seu chefe espera o princípio da responsabilidade única, mas Pylint diz não. Portanto, adicione um segundo método à sua classe para que ela viole o princípio de responsabilidade única. Até onde você deve assumir o princípio da responsabilidade única está nos olhos de quem vê.

Minha correção,

Eu adicionei um método extra à minha classe, então agora ele faz 2 coisas.

def __str__(self):
    return self.__class__.__name__

Só estou me perguntando se preciso dividir minha classe em 2 arquivos separados agora, e talvez módulos também.

problema resolvido, mas não com meus colegas que passam o dia todo discutindo a especificação, ao invés de continuar com ela, como se fosse vida ou morte.

Sean Bradley
fonte