Что означает сообщение pylint «Слишком мало общедоступных методов»

110

Я запускаю pylint для некоторого кода и получаю сообщение об ошибке «Слишком мало общедоступных методов (0/2)». Что означает это сообщение? Документы pylint бесполезны:

Используется, когда в классе слишком мало общедоступных методов, поэтому убедитесь, что оно того стоит.

монсур
источник
1
Как выглядит твой класс? Делает ли класс что-нибудь кроме хранения данных?
Blender
1
Все, что делает класс, это хранит данные.
монсур
2
Ну вот и твоя проблема. Классы не предназначены для хранения данных. Вот для чего нужны такие структуры данных, как словари и списки.
Blender
Интересно, спасибо! Сообщение об ошибке pylint можно было бы сделать более полезным. В любом случае, не стесняйтесь превращать свой комментарий в ответ, и я одобряю.
монсур
6
Но где же определение «немногие»? Получился ровно один способ. Вот почему класс существует. Как pylint определяет «несколько»? Больше двух? Зачем?
Zordid

Ответы:

124

Ошибка в основном говорит о том, что классы не предназначены только для хранения данных, поскольку вы в основном рассматриваете класс как словарь. Классы должны иметь по крайней мере несколько методов для работы с данными, которые они хранят.

Если ваш класс выглядит так:

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

Рассмотрите возможность использования словаря или namedtupleвместо него. Хотя, если класс кажется лучшим выбором, используйте его. pylint не всегда знает, что лучше.

Обратите внимание, что namedtupleон неизменен, и значения, присвоенные при создании экземпляра, не могут быть изменены позже.

Блендер
источник
73
+1 для «pylint не знает, что лучше» - судите сами, но, как правило, если вам нужна «структура», используйте dictили namedtuple. Используйте класс, когда вы хотите добавить некоторую логику к своему объекту (например, вы хотите, чтобы что-то происходило при его создании, вам нужно, чтобы при его добавлении происходили какие-то особые вещи, вы хотите выполнять над ним некоторые операции, контролировать, как его отображается и т. д.)
Burhan Khalid
Спасибо за подробные ответы! Мой вариант использования аналогичен тому, что упоминал Бурхан, я выполняю некоторую обработку данных при их создании.
монсур
6
Эта ошибка не имеет смысла, если у вас есть Meta (метакласс) внутри определения класса.
alexander_ch
12
namedtupleотстой - помимо уродливого синтаксиса, вы не можете задокументировать его или легко указать значения по умолчанию.
rr-
6
Каждый раз, когда я использовал, namedtupleя сожалел о решении. Непоследовательно разрешать как именованные, так и индексированные атрибуты доступа.
theorifice
39

Если вы расширяете класс, я предлагаю систематически отключать это предупреждение и двигаться дальше, например, в случае задач Celery:

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

    ...             

Даже если вы расширяете только одну функцию, вам определенно нужен класс, чтобы эта техника работала, и расширение определенно лучше, чем взлом сторонних классов!

мудрец
источник
Имея этот diable, предварительная фиксация теперь дает мне: Плохое значение параметра 'too-few-public-method' (bad-option-value)
Меркурий
Вы включили методы с буквой s? В вашем сообщении о неверной ценности варианта его нет.
sage
4
Вероятно, лучший способ отключить это - установить min-public-methods=0в [BASIC]разделе файла конфигурации. Это позволяет вам поместить его в отдельную строку от всех ваших disable=вещей (в [MESSAGE CONTROL]), что, как я считаю, упрощает добавление подробных комментариев о том, почему вы включили и отключили что-то вместе с изменением конфигурации.
cjs 05
15

Это еще один случай pylintслепых правил России.

«Классы не предназначены для хранения данных» - это ложное утверждение. Словари подходят не для всего. Член данных класса - это что-то значимое, элемент словаря - это нечто необязательное. Доказательство: вы можете сделать, dictionary.get('key', DEFAULT_VALUE)чтобы предотвратить a KeyError, но по __getattr__умолчанию нет простого .

EDIT - рекомендуемые способы использования структур

Мне нужно обновить свой ответ. Прямо сейчас - если вам нужен struct, у вас есть два отличных варианта:

а) Просто используйте attrs

Это библиотека для этого:

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

Что вы получаете дополнительно: отказ от написания конструкторов, значений по умолчанию, проверки, __repr__объекты только для чтения (для замены namedtuples, даже в Python 2) и многое другое.

б) Используйте dataclasses(Py 3.7+)

Следуя комментарию hwjp, я также рекомендую dataclasses:

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

Это почти так же хорошо, как attrsи стандартный библиотечный механизм («батарейки включены»), без дополнительных зависимостей, кроме Python 3.7+.

Остальная часть предыдущего ответа

NamedTupleне очень хорошо - особенно до Python 3 typing.NamedTuple: https://docs.python.org/3/library/typing.html#typing.NamedTuple - вам определенно следует проверить NamedTupleшаблон «класс, производный от ». Python 2, namedtuplesсозданный из строковых описаний, уродлив, плох, а "программирование внутри строковых литералов" глупо.

Я согласен с двумя текущими ответами («подумайте об использовании чего-то еще, но pylint не всегда правильный» - принятый и «используйте комментарий, подавляющий pylint»), но у меня есть собственное предложение.

Позвольте мне указать на это еще раз: некоторые классы предназначены только для хранения данных.

Теперь возможность также рассмотреть - используйте 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

Выше у вас есть свойства только для чтения, которые подходят для объекта значения (например, как в доменном дизайне), но вы также можете предоставить сеттеры - таким образом ваш класс сможет взять на себя ответственность за поля, которые у вас есть - например для выполнения некоторой проверки и т. д. (если у вас есть сеттеры, вы можете назначить их использование в конструкторе, то есть self.foo = fooвместо прямого self._foo = foo, но осторожного, сеттеры могут предположить, что другие поля уже инициализированы, и тогда вам понадобится настраиваемая проверка в конструкторе) .

Томаш Гандор
источник
2
В Python 3.7 и выше классы данных предоставляют хорошее решение, устраняя некоторые уродства именованных кортежей, и они идеально подходят для объектов значений DDD.
hwjp 02
Я согласен, и с 2020 года это стандартный путь. Чтобы иметь механизм широкого диапазона версий (2.7, 3.3+, если я помню), вы могли использовать attrsбиблиотеку, которая на самом деле была планом для создания dataclassesмодуля.
Tomasz
namedtuplesимеют странный синтаксис наследования ... требуя, чтобы каждый класс, использующий один, знал, что это именованный кортеж, и использовал __new__вместо __init__. dataclassesнет этого ограничения
Эрик Аронести
4

Трудно, когда начальник ожидает принципа единственной ответственности, но Пилинт говорит нет. Так что добавьте второй метод в свой класс, чтобы ваш класс нарушил принцип единой ответственности. Насколько далеко вы собираетесь принять принцип единственной ответственности, - в глазах смотрящего.

Мое исправление,

Я добавил в свой класс дополнительный метод, так что теперь он выполняет две задачи.

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

Мне просто интересно, нужно ли мне теперь разделить мой класс на 2 отдельных файла и, возможно, на модули.

проблема решена, но не с моими коллегами, которые проводят весь день, обсуждая спецификацию, вместо того, чтобы заниматься ею, как будто это жизнь и смерть.

Шон Брэдли
источник