Co oznacza komunikat „Za mało metod publicznych” pylinta

110

Uruchamiam pylint na jakimś kodzie i otrzymuję błąd „Za mało metod publicznych (0/2)”. Co oznacza ta wiadomość? Dokumenty pylint nie są pomocne:

Używane, gdy klasa ma zbyt mało metod publicznych, więc upewnij się, że naprawdę warto.

monsur
źródło
1
Jak wygląda twoja klasa? Czy klasa robi coś innego niż przechowywanie danych?
Blender
1
Wszystko, co robi klasa, to przechowywanie danych.
monsur
2
Cóż, jest twój problem. Klasy nie są przeznaczone do przechowywania danych. Do tego służą struktury danych, takie jak słowniki i listy.
Blender
Ciekawe, dzięki! Komunikat o błędzie pylint może być bardziej przydatny. W każdym razie możesz zamienić swój komentarz w odpowiedź, a ja ją zatwierdzę.
monsur
6
Ale gdzie jest definicja „niewielu”? Mam dokładnie jedną metodę. To jest powód, dla którego ta klasa istnieje. Jak pylint definiuje „kilka”? Więcej niż 2? Czemu?
Zordid,

Odpowiedzi:

124

Błąd zasadniczo mówi, że klasy nie są przeznaczone tylko do przechowywania danych, ponieważ traktujesz w zasadzie klasę jako słownik. Klasy powinny mieć co najmniej kilka metod do operowania na danych, które przechowują.

Jeśli Twoja klasa wygląda tak:

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

Rozważ użycie słownika lub namedtuplezamiast niego. Chociaż jeśli klasa wydaje się najlepszym wyborem, skorzystaj z niej. pylint nie zawsze wie, co jest najlepsze.

Należy pamiętać, że namedtuplejest niezmienny, a wartości przypisane podczas tworzenia wystąpienia nie mogą być później modyfikowane.

Mikser
źródło
72
+1 dla „pylint nie wie, co jest najlepsze” - kieruj się własnym osądem, ale z reguły, jeśli potrzebujesz „struktury”, użyj dictlub namedtuple. Użyj klasy, jeśli chcesz dodać logikę do swojego obiektu (na przykład chcesz, aby coś się wydarzyło, gdy zostanie utworzony, potrzebujesz specjalnych rzeczy, gdy zostanie dodany, chcesz wykonać na nim pewne operacje, kontrolować sposób wyświetlane itp.)
Burhan Khalid,
Dzięki za szczegółowe odpowiedzi! Mój przypadek użycia jest podobny do tego, o którym wspomniał Burhan, przetwarzam dane podczas ich tworzenia.
monsur,
6
Ten błąd nie ma sensu, jeśli w definicji klasy znajduje się Meta (metaklasa).
alexander_ch
11
namedtuplejest do bani - oprócz brzydkiej składni nie można jej łatwo udokumentować ani podać wartości domyślnych.
rr-
6
Za każdym razem namedtupleżałowałem tej decyzji. Niespójne jest zezwalanie na dostęp zarówno nazwany, jak i indeksowany.
teoria
39

Jeśli przedłużasz klasę, to moja sugestia jest taka, aby systematycznie wyłączać to ostrzeżenie i przejść dalej, np. W przypadku zadań Seler:

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

    ...             

Nawet jeśli rozszerzasz tylko jedną funkcję, zdecydowanie potrzebujesz klasy, aby ta technika działała, a rozszerzanie jest zdecydowanie lepsze niż hakowanie klas zewnętrznych!

szałwia
źródło
Posiadanie tego wiarygodnego, wstępnego zatwierdzenia daje mi teraz: Zła wartość opcji „zbyt mało-metoda-publiczna” (zła-wartość-opcji)
Mercury
Czy włączyłeś metody „s”? Twoja wiadomość o złej wartości opcji tego nie zawiera.
mędrzec
4
Prawdopodobnie lepszym sposobem na wyłączenie tego jest ustawienie min-public-methods=0w [BASIC]sekcji pliku konfiguracyjnego. Pozwala to umieścić go w osobnej linii od wszystkich twoich disable=rzeczy (w [MESSAGE CONTROL]), co, jak uważam, ułatwia dodawanie szczegółowych komentarzy na temat tego, dlaczego włączyłeś i wyłączyłeś rzeczy wraz ze zmianą konfiguracji.
cjs
15

To kolejny przypadek pylint ślepych zasad.

„Klasy nie służą do przechowywania danych” - to jest fałszywe stwierdzenie. Słowniki nie nadają się do wszystkiego. Element danych klasy jest znaczący, element słownika jest opcjonalny. Dowód: możesz zrobić, dictionary.get('key', DEFAULT_VALUE)aby zapobiec KeyError, ale nie jest to proste__getattr__ domyślnego.

EDYCJA - zalecane sposoby korzystania ze struktur

Muszę zaktualizować swoją odpowiedź. Teraz - jeśli potrzebujeszstruct , masz dwie świetne opcje:

a) Po prostu użyj attrs

Oto biblioteka do tego:

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

Co zyskujesz dodatkowo: nie pisanie konstruktorów, wartości domyślne, walidacja, __repr__obiekty tylko do odczytu (do zastąpienianamedtuples , nawet w Pythonie 2) i więcej.

b) Użyj dataclasses (Py 3.7+)

Idąc za komentarzem hwjp, polecam również dataclasses:

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

To jest prawie tak dobre, jak attrs i jest to standardowy mechanizm biblioteki („w zestawie baterie”), bez dodatkowych zależności, z wyjątkiem Pythona 3.7+.

Reszta poprzedniej odpowiedzi

NamedTuplenie jest świetny - szczególnie przed Pythonem 3 typing.NamedTuple: https://docs.python.org/3/library/typing.html#typing.NamedTuple - zdecydowanie powinieneś sprawdzić NamedTuplewzorzec „klasa pochodna ”. Python 2 -namedtuples stworzony z opisów łańcuchów - jest brzydki, zły i "programowanie wewnątrz literałów łańcuchowych" jest głupie.

Zgadzam się z dwiema obecnymi odpowiedziami („rozważ użycie czegoś innego, ale pylint nie zawsze ma rację” - przyjęta i „użyj komentarza tłumiącego pylint”), ale mam własną sugestię.

Pozwólcie, że jeszcze raz zwrócę uwagę: niektóre klasy służą tylko do przechowywania danych.

Teraz opcja do rozważenia - użyj 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

Powyżej masz właściwości tylko do odczytu, co jest OK dla obiektu wartości (np. Jak te w Domain Driven Design), ale możesz także podać setery - w ten sposób twoja klasa będzie mogła wziąć odpowiedzialność za pola, które masz - na przykład do wykonania walidacji itp. (jeśli masz setery, możesz przypisać je używając ich w konstruktorze, tj. self.foo = foozamiast bezpośrednich self._foo = foo, ale ostrożnie, setery mogą założyć, że inne pola są już zainicjowane, a wtedy potrzebujesz niestandardowej walidacji w konstruktorze) .

Tomasz Gandor
źródło
2
W Pythonie 3.7 i nowszych, klasy danych stanowią dobre rozwiązanie, rozwiązując niektóre z brzydoty nazwanych krotek i są idealne dla obiektów wartości DDD.
hwjp
Zgadzam się i od 2020 roku jest to standardowa droga. Aby mieć mechanizm o szerokim zakresie wersji (2.7, 3.3+, jeśli dobrze pamiętam), możesz użyć attrsbiblioteki, która była tak naprawdę planem tworzenia dataclassesmodułu.
Tomasz Gandor
namedtuplesmają dziwną składnię do dziedziczenia ... wymagając od każdej klasy używającej jednej, aby wiedziała, że ​​jest to nazwana krotka i używa __new__zamiast __init__. dataclassesnie ma tego ograniczenia
Erik Aronesty
4

Trudno jest, gdy szef oczekuje zasady pojedynczej odpowiedzialności, ale pylint mówi nie. Więc dodaj drugą metodę do swojej klasy, aby Twoja klasa naruszyła zasadę pojedynczej odpowiedzialności. To, jak daleko masz się podjąć, zasada pojedynczej odpowiedzialności jest w oczach patrzącego.

Moja poprawka,

Dodałem dodatkową metodę do mojej klasy, więc teraz robi dwie rzeczy.

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

Zastanawiam się tylko, czy muszę teraz podzielić moją klasę na 2 oddzielne pliki, a może również moduły.

problem rozwiązany, ale nie z moimi kolegami, którzy spędzają cały dzień dyskutując ze specyfikacją, zamiast zajmować się nią, jakby to było życie i śmierć.

Sean Bradley
źródło