161

I'm running Pylint on some code, and receiving the error "Too few public methods (0/2)". What does this message mean?

The Pylint documentation is not helpful:

Used when a class has too few public methods, so be sure it's really worth it.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
monsur
  • 45,581
  • 16
  • 101
  • 95
  • 2
    What does your class look like? Does the class do anything other than store data? – Blender Dec 25 '12 at 03:23
  • 2
    All the class does is store data. – monsur Dec 25 '12 at 03:31
  • 2
    Well, there's your problem. Classes aren't meant to store data. That's what data structures like dictionaries and lists are for. – Blender Dec 25 '12 at 03:32
  • 1
    Interesting, thanks! The pylint error message could be made more useful. Anyway, feel free to turn your comment into an answer and I'll approve. – monsur Dec 25 '12 at 03:41
  • 15
    But where's the definition of "few"? I got exactly one method. That's the reason the class exists. How does pylint define "few"? More than 2? Why? – Zordid Aug 21 '18 at 12:40

4 Answers4

166

The error basically says that classes aren't meant to just store data, as you're basically treating the class as a dictionary. Classes should have at least a few methods to operate on the data that they hold.

If your class looks like this:

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

Consider using a dictionary or a namedtuple instead. Although if a class seems like the best choice, use it. Pylint doesn't always know what's best.

Do note that namedtuple is immutable and the values assigned on instantiation cannot be modified later.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Blender
  • 289,723
  • 53
  • 439
  • 496
  • 103
    +1 for "pylint doesn't know what's best" - use your own judgement but as a rule, if what you need is a "struct", use a `dict` or `namedtuple`. Use a class when you want to add some logic to your object (for example, you want stuff to happen when it is created, you need some special things to happen when its added, you want o perform some operations on it, control how its displayed, etc.) – Burhan Khalid Dec 25 '12 at 04:34
  • 8
    This error does not make sense if you have Meta (metaclass) inside you class definition. – alexander_ch Jan 18 '15 at 12:47
  • 26
    `namedtuple` sucks - on top of having ugly syntax, you can't document it or provide default values easily. – rr- Apr 03 '16 at 16:34
  • 15
    Every time I've used `namedtuple` I've regretted the decision. It's inconsistent to allow both named access and indexed access attributes. – theorifice Dec 15 '16 at 20:27
  • Also, you can't use typings when using `namedtuple` in python 3.5+. Instead you can create class that inherits from `typing.NamedTuple` but then you will get the same error from pylint. I prefer to use classes with typings and ignore that error, so that makes your code more readable. – Nerlin Oct 29 '17 at 10:51
  • 4
    This crops up even when inheriting from a class that might have loads of methods, e.g. a SQLAlchemy Model, so it's definitely just advisory. – Rob Grant Jan 04 '18 at 08:30
  • 1
    This makes no sense to me. I just wanted a plain old data structure. The whole point of the class was to give a name to that data structure! – Hemil Aug 17 '20 at 07:56
  • 18
    There are now `dataclasses`: https://docs.python.org/3/library/dataclasses.html – hildensia Dec 10 '20 at 13:07
  • 1
    Okay,but this warning makes no sense for a class that has 1 method and a bunch of variables. The whole point of a class is to combine data and a method so I don't have to write a function with a bunch of `if hasattr(func, ...)` which is very much frowned upon. – SurpriseDog Apr 09 '21 at 18:00
  • Thanks @hildensia, dataclasses is exactly what I needed to define a class without public methods! – Andreas L. Nov 02 '22 at 16:13
56

If you are extending a class, then my suggestion is to systematically disable this warning and move on, e.g., in the case of Celery tasks:

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

    ...             

Even if you are only extending a single function, you definitely need a class to make this technique function, and extending is definitely better than hacking on the third-party classes!

sage
  • 4,863
  • 2
  • 44
  • 47
  • Having this diable, pre-commit now gives me: Bad option value 'too-few-public-method' (bad-option-value) – Mercury Jan 16 '18 at 14:26
  • 1
    Did you include the 's' on methods? Your bad-option-value message does not have it. – sage Feb 11 '18 at 22:16
  • 9
    Probably a better way to disable this is to set `min-public-methods=0` in the `[BASIC]` section of the config file. This lets you put it on a separate line from all your `disable=` stuff (in `[MESSAGE CONTROL]`) which I find makes easier adding detailed comments about why you enabled and disabled things along with the config change. – cjs Mar 05 '18 at 03:41
34

This is another case of Pylint's blind rules.

"Classes are not meant to store data" - this is a false statement. Dictionaries are not good for everything. A data member of a class is something meaningful, a dictionary item is something optional. Proof: you can do dictionary.get('key', DEFAULT_VALUE) to prevent a KeyError, but there is no simple __getattr__ with default.

Recommended ways for using structs

I need to update my answer. Right now - if you need a struct, you have two great options:

a) Just use attrs

These is a library for that:

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

What you get extra: not writing constructors, default values, validation, __repr__, read-only objects (to replace namedtuples, even in Python 2) and more.

b) Use dataclasses (Py 3.7+)

Following hwjp's comment, I also recommend dataclasses:

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

This is almost as good as attrs, and is a standard library mechanism ("batteries included"), with no extra dependencies, except Python 3.7+.

The rest of the previous answer

NamedTuple is not great - especially before Python 3's typing.NamedTuple: https://docs.python.org/3/library/typing.html#typing.NamedTuple

  • you definitely should check out the "class derived from NamedTuple" pattern. Python 2 - namedtuples created from string descriptions - is ugly, bad and "programming inside string literals" stupid.

I agree with the two current answers ("consider using something else, but Pylint isn't always right" - the accepted one, and "use Pylint suppressing comment"), but I have my own suggestion.

Let me point this out one more time: Some classes are meant just to store data.

Now the option to also consider - 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

Above you have read-only properties, which is OK for Value Object (e.g., like those in Domain Driven Design), but you can also provide setters - this way your class will be able to take responsibility for the fields which you have - for example to do some validation etc. (if you have setters, you can assign using them in the constructor, i.e., self.foo = foo instead of direct self._foo = foo, but careful, the setters may assume other fields to be initialized already, and then you need custom validation in the constructor).

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Tomasz Gandor
  • 8,235
  • 2
  • 60
  • 55
  • 2
    In Python 3.7 and above, dataclasses provide a good solution, addressing some of the ugliness of namedtuples, and they're _perfect_ for DDD Value Objects. – hwjp Apr 02 '19 at 23:46
  • I agree, and from 2020 on it's the standard way to go. To have a wide-version-range mechanism (2.7, 3.3+ if I remember) you could use the `attrs` library, which was actually the blueprint for creating the `dataclasses` module. – Tomasz Gandor Apr 03 '19 at 09:55
  • `namedtuples` have weird syntax for inheritance ... requiring every class using one to know that it's a named tuple and use `__new__` instead of `__init__`. `dataclasses` don't have this limitation – Erik Aronesty Jul 31 '19 at 15:41
  • I like dataclasses. My only problem is that I can't attach a docstring to the generated attributes. – Apoorv Nov 20 '20 at 19:41
6

It's hard when your boss expects the single responsibility principle, but Pylint says no. So add a second method to your class so your class violates the single responsibility principle. How far you are meant to take the single responsibility principle is in the eye of the beholder.

My fix

I added an extra method to my class, so it now does two things.

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

I'm just wondering if I need to split my class into two separate files now, and maybe modules as well.

The problem is solved, but not with my colleagues who spend all day arguing about the specification, rather than getting on with it, like it's life and death.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Sean Bradley
  • 3,254
  • 1
  • 17
  • 10
  • 2
    I think that is a problematic approach. You should never just "add stuff because some check tells you to". This method does not add any other value than telling Pylint to stop complaining, it adds extra code and extra code steals focus, and adds technical debt. Instead what you should do is disable the check locally, by adding this comment: `# pylint: disable=R0903` by doing this you are telling the reader that you acknowledge the Pylint output, but that you have taken a deliberate decision to ignore it here. – Tommy Andersen May 10 '22 at 11:35
  • @TommyAndersen Yes, I doubt Sean was really telling us to do that. It looked more like sarcasm to me – Ian Goldby Jul 13 '22 at 14:55
  • 1
    @IanGoldby perhaps but sarcasm is not really good on a site like this – Tommy Andersen Jul 14 '22 at 21:09