¿Qué significa el mensaje "Muy pocos métodos públicos" de pylint?

110

Estoy ejecutando pylint en algún código y recibo el error "Muy pocos métodos públicos (0/2)". ¿Qué significa este mensaje? Los documentos de pylint no son útiles:

Se usa cuando la clase tiene muy pocos métodos públicos, así que asegúrese de que realmente valga la pena.

monsur
fuente
1
¿Cómo es tu clase? ¿La clase hace algo más que almacenar datos?
Blender
1
Todo lo que hace la clase es almacenar datos.
monsur
2
Bueno, ahí está tu problema. Las clases no están destinadas a almacenar datos. Para eso están las estructuras de datos como los diccionarios y las listas.
Blender
Interesante, gracias! El mensaje de error de pylint podría resultar más útil. De todos modos, siéntete libre de convertir tu comentario en una respuesta y la aprobaré.
monsur
6
Pero, ¿dónde está la definición de "pocos"? Tengo exactamente un método. Esa es la razón por la que existe la clase. ¿Cómo define pylint a "pocos"? ¿Más de 2? ¿Por qué?
Zordid

Respuestas:

124

El error básicamente dice que las clases no tienen la intención de simplemente almacenar datos, como que está básicamente el tratamiento de la clase como un diccionario. Las clases deben tener al menos algunos métodos para operar con los datos que poseen.

Si su clase se ve así:

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

Considere usar un diccionario o en su namedtuplelugar. Aunque si una clase te parece la mejor opción, úsala. pylint no siempre sabe qué es lo mejor.

Tenga en cuenta que namedtuplees inmutable y los valores asignados en la instanciación no se pueden modificar más adelante.

Licuadora
fuente
72
+1 para "pylint no sabe qué es lo mejor": use su propio criterio, pero como regla, si lo que necesita es una "estructura", use una dicto namedtuple. Usa una clase cuando quieras agregar algo de lógica a tu objeto (por ejemplo, quieres que sucedan cosas cuando se crea, necesitas que sucedan algunas cosas especiales cuando se agregue, quieres realizar algunas operaciones en él, controlar cómo está mostrado, etc.)
Burhan Khalid
¡Gracias por las respuestas detalladas! Mi caso de uso es similar al que mencionó Burhan, estoy procesando los datos cuando se crean.
monsur
6
Este error no tiene sentido si tiene Meta (metaclase) dentro de su definición de clase.
alexander_ch
11
namedtupleapesta: además de tener una sintaxis desagradable, no puede documentarla o proporcionar valores predeterminados fácilmente.
rr-
6
Cada vez que lo he usado namedtupleme arrepiento de la decisión. Es inconsistente permitir tanto el acceso con nombre como los atributos de acceso indexado.
theorifice
39

Si está ampliando una clase, mi sugerencia es deshabilitar sistemáticamente esta advertencia y continuar, por ejemplo, en el caso de las tareas de Apio:

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

    ...             

Incluso si solo está extendiendo una única función, definitivamente necesita una clase para hacer que esta técnica funcione, ¡y extender es definitivamente mejor que piratear las clases de terceros!

sabio
fuente
Tener este diable, pre-commit ahora me da: Mal valor de opción 'too-few-public-method' (bad-option-value)
Mercury
¿Incluyó la 's' en los métodos? Su mensaje de valor de opción incorrecta no lo tiene.
sabio
4
Probablemente una mejor manera de deshabilitar esto es establecerlo min-public-methods=0en la [BASIC]sección del archivo de configuración. Esto le permite colocarlo en una línea separada de todas sus disable=cosas (en [MESSAGE CONTROL]), lo que me parece que facilita la adición de comentarios detallados sobre por qué habilitó y deshabilitó cosas junto con el cambio de configuración.
cjs
15

Este es otro caso de pylintlas reglas ciegas.

"Las clases no están destinadas a almacenar datos": esta es una declaración falsa. Los diccionarios no sirven para todo. Un miembro de datos de una clase es algo significativo, un elemento de diccionario es algo opcional. Prueba: puedes hacer dictionary.get('key', DEFAULT_VALUE)para prevenir una KeyError, pero no hay nada simple __getattr__con defecto.

EDITAR - formas recomendadas de usar estructuras

Necesito actualizar mi respuesta. Ahora mismo, si necesita un struct, tiene dos excelentes opciones:

a) Solo usa attrs

Esta es una biblioteca para eso:

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

Lo que obtiene extra: no escribir constructores, valores predeterminados, validación, __repr__objetos de solo lectura (para reemplazar namedtuples, incluso en Python 2) y más.

b) Utilizar dataclasses(Py 3.7+)

Siguiendo el comentario de hwjp, también recomiendo dataclasses:

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

Esto es casi tan bueno attrsy es un mecanismo de biblioteca estándar ("baterías incluidas"), sin dependencias adicionales, excepto Python 3.7+.

Resto de respuesta anterior

NamedTupleno es genial, especialmente antes de Python 3 typing.NamedTuple: https://docs.python.org/3/library/typing.html#typing.NamedTuple - definitivamente deberías revisar el NamedTuplepatrón de "clase derivada de ". Python 2 - namedtuplescreado a partir de descripciones de cadenas - es feo, malo y "programar dentro de cadenas literales" estúpido.

Estoy de acuerdo con las dos respuestas actuales ("considere usar otra cosa, pero pylint no siempre es correcto" - la aceptada y "use pylint para suprimir el comentario"), pero tengo mi propia sugerencia.

Permítanme señalar esto una vez más: algunas clases están diseñadas solo para almacenar datos.

Ahora la opción a considerar también - 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

Arriba tiene propiedades de solo lectura, que están bien para Objeto de valor (por ejemplo, como las del Diseño controlado por dominio), pero también puede proporcionar establecedores; de esta manera, su clase podrá asumir la responsabilidad de los campos que tiene, por ejemplo para hacer alguna validación, etc. (si tiene establecedores, puede asignarlos usándolos en el constructor, es decir, en self.foo = foolugar de directos self._foo = foo, pero con cuidado, los establecedores pueden asumir que otros campos ya están inicializados, y luego necesita una validación personalizada en el constructor) .

Tomasz Gandor
fuente
2
En Python 3.7 y versiones posteriores, las clases de datos brindan una buena solución, abordando algunas de las fealdades de las tuplas con nombre, y son perfectas para los objetos de valor DDD.
hwjp
Estoy de acuerdo, y a partir de 2020 es el camino estándar a seguir. Para tener un mecanismo de amplio rango de versiones (2.7, 3.3+, si recuerdo), podría usar la attrsbiblioteca, que en realidad era el modelo para crear el dataclassesmódulo.
Tomasz Gandor
namedtuplestienen una sintaxis extraña para la herencia ... requiriendo que cada clase que use una sepa que es una tupla con nombre y use en __new__lugar de __init__. dataclassesno tengo esta limitación
Erik Aronesty
4

Es difícil cuando su jefe espera el principio de responsabilidad única, pero pylint dice que no. Así que agregue un segundo método a su clase para que su clase viole el principio de responsabilidad única. Hasta dónde se supone que debe llevar el principio de responsabilidad única está en el ojo del espectador.

Mi solución

Agregué un método adicional a mi clase, por lo que ahora hace 2 cosas.

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

Me pregunto si necesito dividir mi clase en 2 archivos separados ahora, y tal vez también en módulos.

problema resuelto, pero no con mis colegas que pasan todo el día discutiendo las especificaciones, en lugar de seguir adelante, como si fuera de vida o muerte.

Sean Bradley
fuente