Cosa significa il messaggio "Troppo pochi metodi pubblici" di pylint


110

Sto eseguendo pylint su un codice e ricevo l'errore "Troppi pochi metodi pubblici (0/2)". Cosa significa questo messaggio? I documenti di pylint non sono utili:

Usato quando la classe ha troppo pochi metodi pubblici, quindi assicurati che ne valga davvero la pena.


1
Che aspetto ha la tua classe? La classe fa qualcosa di diverso dall'archiviazione dei dati?
Blender

1
Tutto ciò che la classe fa è memorizzare i dati.
monsur

2
Bene, ecco il tuo problema. Le classi non sono destinate a memorizzare dati. Ecco a cosa servono le strutture dati come dizionari ed elenchi.
Blender

Interessante, grazie! Il messaggio di errore del pilastro potrebbe essere reso più utile. Ad ogni modo, sentiti libero di trasformare il tuo commento in una risposta e io approverò.
monsur

6
Ma dov'è la definizione di "pochi"? Ho esattamente un metodo. Questo è il motivo per cui esiste la classe. Come definisce pylint "pochi"? Più di 2? Perché?
Zordid

Risposte:


124

L'errore dice fondamentalmente che le classi non sono destinate a memorizzare solo dati, poiché in pratica stai trattando la classe come un dizionario. Le classi dovrebbero avere almeno alcuni metodi per operare sui dati in loro possesso.

Se la tua classe ha questo aspetto:

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

Considera l'idea di utilizzare un dizionario o un namedtuple. Anche se una classe sembra la scelta migliore, usala. Pylint non sa sempre cosa è meglio.

Tieni presente che namedtupleè immutabile e che i valori assegnati all'istanza non possono essere modificati in seguito.


72
+1 per "pylint non sa cosa è meglio" - usa il tuo giudizio ma di regola, se ciò di cui hai bisogno è una "struttura", usa un dicto namedtuple. Usa una classe quando vuoi aggiungere un po 'di logica al tuo oggetto (ad esempio, vuoi che le cose accadano quando viene creato, hai bisogno che accadano alcune cose speciali quando viene aggiunto, vuoi o eseguire alcune operazioni su di esso, controllare come è visualizzato, ecc.)
Burhan Khalid

Grazie per le risposte dettagliate! Il mio caso d'uso è simile a quello menzionato da Burhan, sto eseguendo alcune elaborazioni sui dati quando vengono creati.
monsur

6
Questo errore non ha senso se hai Meta (metaclasse) nella definizione della tua classe.
alexander_ch

11
namedtuplefa schifo - oltre ad avere una sintassi brutta, non puoi documentarla o fornire facilmente i valori predefiniti.
rr-

6
Ogni volta che l'ho usato namedtuplemi sono pentito della decisione. Non è coerente consentire sia l'accesso denominato che gli attributi di accesso indicizzato.
teorico

39

Se stai estendendo una classe, il mio suggerimento è di disabilitare sistematicamente questo avviso e andare avanti, ad esempio, nel caso delle attività di Celery:

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

    ...             

Anche se stai estendendo solo una singola funzione, hai sicuramente bisogno di una classe per far funzionare questa tecnica, ed estenderla è decisamente meglio che hackerare le classi di terze parti!


Avere questo diable, pre-commit ora mi dà: Valore dell'opzione errato "too-few-public-method" (valore-opzione-cattivo)
Mercury

Hai incluso la "s" sui metodi? Il tuo messaggio con valore di opzione errato non ce l'ha.
saggio

4
Probabilmente un modo migliore per disabilitare questo è impostare min-public-methods=0nella [BASIC]sezione del file di configurazione. Questo ti consente di metterlo su una riga separata da tutte le tue disable=cose (in [MESSAGE CONTROL]) che trovo facilita l'aggiunta di commenti dettagliati sul motivo per cui hai abilitato e disabilitato le cose insieme alla modifica della configurazione.
cjs

15

Questo è un altro caso delle pylintregole cieche di S.

"Le classi non hanno lo scopo di memorizzare dati" - questa è una dichiarazione falsa. I dizionari non vanno bene per tutto. Un membro dati di una classe è qualcosa di significativo, un elemento del dizionario è qualcosa di opzionale. Prova: puoi fare dictionary.get('key', DEFAULT_VALUE)per prevenire un KeyError, ma non è semplice __getattr__con predefinito.

MODIFICA: modi consigliati per utilizzare gli struct

Devo aggiornare la mia risposta. In questo momento, se hai bisogno di un struct, hai due ottime opzioni:

a) Basta usare attrs

Questa è una libreria per questo:

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

Cosa ottieni in più: non scrivere costruttori, valori predefiniti, convalida, __repr__oggetti di sola lettura (da sostituire namedtuples, anche in Python 2) e altro.

b) Usa dataclasses(Py 3.7+)

Seguendo il commento di hwjp, consiglio anche dataclasses:

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

Questo è quasi buono quanto attrs, ed è un meccanismo di libreria standard ("batterie incluse"), senza dipendenze extra, tranne Python 3.7+.

Resto della risposta precedente

NamedTuplenon è eccezionale - specialmente prima di python 3 typing.NamedTuple: https://docs.python.org/3/library/typing.html#typing.NamedTuple - dovresti assolutamente controllare il NamedTuplepattern "class derivato da ". Python 2 - namedtuplescreato da descrizioni di stringhe - è brutto, cattivo e stupido "programmare all'interno di stringhe letterali".

Sono d'accordo con le due risposte attuali ("considera di usare qualcos'altro, ma pylint non è sempre giusto" - quella accettata, e "usa il commento di soppressione pylint"), ma ho il mio suggerimento.

Lasciatemelo sottolineare ancora una volta: alcune classi sono pensate solo per memorizzare i dati.

Ora l'opzione da considerare anche - usa 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

Sopra hai proprietà di sola lettura, che va bene per Value Object (ad es. Come quelli in Domain Driven Design), ma puoi anche fornire setter: in questo modo la tua classe sarà in grado di assumersi la responsabilità dei campi che hai, ad esempio per fare un po 'di convalida ecc. (se hai setter, puoi assegnarli usandoli nel costruttore, cioè self.foo = fooinvece che direttamente self._foo = foo, ma attenzione, i setter possono presumere che altri campi siano già inizializzati, e quindi hai bisogno di una validazione personalizzata nel costruttore) .


2
In Python 3.7 e versioni successive, le classi di dati forniscono una buona soluzione, affrontando parte della bruttezza delle coppie denominate e sono perfette per gli oggetti valore DDD.
hwjp

Sono d'accordo e dal 2020 in poi è la strada standard da percorrere. Per avere un meccanismo di ampia gamma di versioni (2.7, 3.3+ se ricordo) potresti usare la attrslibreria, che in realtà era il progetto per la creazione del dataclassesmodulo.
Tomasz Gandor

namedtupleshanno una strana sintassi per l'ereditarietà ... richiedendo a ogni classe che ne usa una di sapere che è una tupla con nome e di usare __new__invece di __init__. dataclassesnon hanno questa limitazione
Erik Aronesty

4

È difficile quando il tuo capo si aspetta un principio di responsabilità unica, ma pylint dice di no. Quindi aggiungi un secondo metodo alla tua classe in modo che la tua classe violi il principio di responsabilità unica. Fino a che punto sei destinato a prendere il principio della responsabilità unica è negli occhi di chi guarda.

La mia correzione,

Ho aggiunto un metodo extra alla mia classe, quindi ora fa 2 cose.

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

Mi chiedo solo se ora devo dividere la mia classe in 2 file separati e forse anche i moduli.

problema risolto, ma non con i miei colleghi che passano tutto il giorno a discutere le specifiche, piuttosto che andare avanti con esso, come se fosse la vita o la morte.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.