Que signifie le message "Trop peu de méthodes publiques" de pylint

110

J'exécute pylint sur du code et je reçois l'erreur "Trop peu de méthodes publiques (0/2)". Que signifie ce message? Les documents pylint ne sont pas utiles:

Utilisé lorsque la classe a trop peu de méthodes publiques, alors assurez-vous que cela en vaut vraiment la peine.

Monsur
la source
1
À quoi ressemble votre classe? La classe fait-elle autre chose que stocker des données?
Blender
1
La classe ne fait que stocker des données.
monsur
2
Eh bien, voici votre problème. Les classes ne sont pas destinées à stocker des données. C'est à cela que servent les structures de données comme les dictionnaires et les listes.
Blender
Intéressant, merci! Le message d'erreur de pylint pourrait être rendu plus utile. Quoi qu'il en soit, n'hésitez pas à transformer votre commentaire en réponse et j'approuverai.
monsur
6
Mais où est la définition de «peu»? J'ai exactement une méthode. C'est la raison pour laquelle la classe existe. Comment le pylint définit-il «peu»? Plus de 2? Pourquoi?
Zordid

Réponses:

124

L'erreur dit essentiellement que les classes ne sont pas destinées à uniquement stocker des données, car vous traitez fondamentalement la classe comme un dictionnaire. Les classes doivent avoir au moins quelques méthodes pour opérer sur les données qu'elles contiennent.

Si votre classe ressemble à ceci:

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

Pensez à utiliser un dictionnaire ou un namedtuple. Bien que si une classe semble être le meilleur choix, utilisez-la. pylint ne sait pas toujours ce qui est le mieux.

Notez que namedtuplec'est immuable et que les valeurs attribuées lors de l'instanciation ne peuvent pas être modifiées ultérieurement.

Mixeur
la source
73
+1 pour "pylint ne sait pas ce qu'il y a de mieux" - utilisez votre propre jugement, mais en règle générale, si vous avez besoin d'un "struct", utilisez un dictou namedtuple. Utilisez une classe lorsque vous souhaitez ajouter de la logique à votre objet (par exemple, vous voulez que des choses se produisent lors de sa création, vous avez besoin que des choses spéciales se produisent lorsqu'il est ajouté, vous voulez effectuer des opérations dessus, contrôler la façon dont il affiché, etc.)
Burhan Khalid
Merci pour les réponses détaillées! Mon cas d'utilisation est similaire à ce que Burhan a mentionné, je suis en train de traiter les données lors de leur création.
monsur
6
Cette erreur n'a pas de sens si vous avez Meta (métaclasse) dans votre définition de classe.
alexander_ch
12
namedtuplecraint - en plus d'avoir une syntaxe laide, vous ne pouvez pas la documenter ou fournir facilement des valeurs par défaut.
rr-
6
Chaque fois que j'ai utilisé, namedtuplej'ai regretté la décision. Il est incohérent d'autoriser à la fois l'accès nommé et les attributs d'accès indexés.
théorifice
39

Si vous étendez une classe, alors ma suggestion est de désactiver systématiquement cet avertissement et de passer à autre chose, par exemple dans le cas des tâches Celery:

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

    ...             

Même si vous n'étendez qu'une seule fonction, vous avez certainement besoin d'une classe pour faire fonctionner cette technique, et l'extension est certainement meilleure que le piratage des classes tierces!

sauge
la source
Avoir ce diable, pré-commit me donne maintenant: Mauvaise valeur d'option 'too-few-public-method' (mauvaise-option-value)
Mercury
Avez-vous inclus les «s» sur les méthodes? Votre message de valeur d'option incorrecte ne l'a pas.
sage
4
Une meilleure façon de désactiver cela est probablement de définir min-public-methods=0dans la [BASIC]section du fichier de configuration. Cela vous permet de le mettre sur une ligne distincte de tous vos disable=trucs (in [MESSAGE CONTROL]), ce qui, selon moi, facilite l'ajout de commentaires détaillés sur les raisons pour lesquelles vous avez activé et désactivé les choses avec le changement de configuration.
cjs
15

C'est un autre cas des pylintrègles aveugles de 's.

«Les classes ne sont pas destinées à stocker des données» - c'est une fausse déclaration. Les dictionnaires ne sont pas bons pour tout. Un membre de données d'une classe est quelque chose de significatif, un élément de dictionnaire est quelque chose de facultatif. Preuve: on peut faire dictionary.get('key', DEFAULT_VALUE)pour empêcher un KeyError, mais il n'y a pas de simple __getattr__avec le défaut.

EDIT - méthodes recommandées pour utiliser les structures

J'ai besoin de mettre à jour ma réponse. En ce moment - si vous en avez besoin struct, vous avez deux bonnes options:

a) Il suffit d'utiliser attrs

Voici une bibliothèque pour cela:

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()

Ce que vous obtenez en plus: ne pas écrire de constructeurs, valeurs par défaut, validation, __repr__objets en lecture seule (à remplacer namedtuples, même en Python 2) et plus encore.

b) Utiliser dataclasses(Py 3.7+)

Suite au commentaire de hwjp, je recommande également dataclasses:

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

C'est presque aussi bon que attrs, et c'est un mécanisme de bibliothèque standard ("piles incluses"), sans dépendances supplémentaires, sauf Python 3.7+.

Reste de la réponse précédente

NamedTuplen'est pas génial - surtout avant python 3 typing.NamedTuple: https://docs.python.org/3/library/typing.html#typing.NamedTuple - vous devriez certainement vérifier le NamedTuplemodèle "classe dérivée de ". Python 2 - namedtuplescréé à partir de descriptions de chaînes - est moche, mauvais et "programmer dans des chaînes littérales" stupide.

Je suis d'accord avec les deux réponses actuelles ("envisager d'utiliser autre chose, mais pylint n'est pas toujours correct" - la réponse acceptée, et "utiliser le commentaire de suppression de pylint"), mais j'ai ma propre suggestion.

Permettez-moi de le souligner une fois de plus: certaines classes sont destinées uniquement à stocker des données.

Maintenant, l'option à considérer également - utilisez 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

Ci-dessus, vous avez des propriétés en lecture seule, ce qui est OK pour Value Object (par exemple, comme celles de Domain Driven Design), mais vous pouvez également fournir des setters - de cette façon, votre classe sera en mesure d'assumer la responsabilité des champs que vous avez - par exemple pour faire des validations etc. (si vous avez des setters, vous pouvez les assigner en les utilisant dans le constructeur, c'est-à-dire self.foo = fooau lieu de direct self._foo = foo, mais attention, les setters peuvent supposer que d'autres champs sont déjà initialisés, et alors vous avez besoin d'une validation personnalisée dans le constructeur) .

Tomasz Gandor
la source
2
Dans Python 3.7 et supérieur, les classes de données fournissent une bonne solution, traitant une partie de la laideur des objets nommés, et elles sont parfaites pour les objets de valeur DDD.
hwjp
Je suis d'accord, et à partir de 2020, c'est la voie à suivre standard. Pour avoir un mécanisme à large gamme de versions (2.7, 3.3+ si je me souviens bien), vous pouvez utiliser la attrsbibliothèque, qui était en fait le plan de création du dataclassesmodule.
Tomasz Gandor
namedtuplesont une syntaxe étrange pour l'héritage ... exigeant que chaque classe qui en utilise un sache qu'il s'agit d'un tuple nommé et utilise à la __new__place de __init__. dataclassesne pas avoir cette limitation
Erik Aronesty
4

C'est difficile quand votre patron s'attend à un principe de responsabilité unique, mais le pylint dit non. Ajoutez donc une deuxième méthode à votre classe afin que votre classe enfreigne le principe de responsabilité unique. Dans quelle mesure vous êtes censé prendre le principe de responsabilité unique est dans l'œil du spectateur.

Ma solution,

J'ai ajouté une méthode supplémentaire à ma classe, donc elle fait maintenant 2 choses.

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

Je me demande simplement si je dois diviser ma classe en 2 fichiers séparés maintenant, et peut-être en modules également.

problème résolu, mais pas avec mes collègues qui passent toute la journée à discuter des spécifications, plutôt que de continuer, comme si c'était la vie ou la mort.

Sean Bradley
la source