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.
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.
Odpowiedzi:
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 namedtuple
zamiast niego. Chociaż jeśli klasa wydaje się najlepszym wyborem, skorzystaj z niej. pylint nie zawsze wie, co jest najlepsze.
Należy pamiętać, że namedtuple
jest niezmienny, a wartości przypisane podczas tworzenia wystąpienia nie mogą być później modyfikowane.
dict
lub 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.)
namedtuple
jest do bani - oprócz brzydkiej składni nie można jej łatwo udokumentować ani podać wartości domyślnych.
namedtuple
żałowałem tej decyzji. Niespójne jest zezwalanie na dostęp zarówno nazwany, jak i indeksowany.
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!
min-public-methods=0
w [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.
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.
Muszę zaktualizować swoją odpowiedź. Teraz - jeśli potrzebujeszstruct
, masz dwie świetne opcje:
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.
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+.
NamedTuple
nie jest świetny - szczególnie przed Pythonem 3 typing.NamedTuple
:
https://docs.python.org/3/library/typing.html#typing.NamedTuple
- zdecydowanie powinieneś sprawdzić NamedTuple
wzorzec „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 = foo
zamiast 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) .
attrs
biblioteki, która była tak naprawdę planem tworzenia dataclasses
modułu.
namedtuples
mają 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__
. dataclasses
nie ma tego ograniczenia
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ć.