python

PEP 8 – czyli gramatyka dla programisty

PEP 8 i PEP 257,taby czy spacje, docstring

Każdy język ma własną gramatykę, czyli zbiór reguł określających zasady tworzenia wypowiedzi. To samo dotyczy również języków programowania. W przypadku Pythona dokumentami definiującymi reguły gramatyczne są PEP 8 oraz PEP 257.

PEP czyli Python Enhancement Proposal jest oficjalnym dokumentem informacyjnym dla społeczności programistów Pythona. Dokumenty tego typu służą między innymi jako dokumentacja nowych funkcji języka. Aktualnie wydanych zostało ponad 470 PEPs. Ich pełną listę można znaleźć w treści PEP 0.

W tym poście chciałbym zwrócić uwagę na dokumenty PEP 8 (Style Guide for Python Code) oraz PEP 257 (Docstring Conventions). Określają one w jaki sposób powinniśmy tworzyć kod.

Dlaczego warto je znać?

Jedną z zalet wynikającą z istnienia norm gramatyki języka programowania jest czytelność kodu, co jest szczególnie ważne w przypadku omawianego języka, który czytelność stawia na pierwszym miejscu. Konwencje przedstawione w dokumentach PEPs są dobrze przemyślane i zdecydowanie poprawiają czytelność. Co prawda, to czy kod jest czytelny jest względne, jednak dokumenty te zapewniają również jednolitość. Gdy już przyzwyczaimy się do zaproponowanych zasad, będziemy mogli bez problemu analizować kod napisany przez innych programistów.

Spacje czy taby?

Podczas programistycznych dyskusji bardzo częstym powodem do sprzeczek jest sposób tworzenia wcięć w kodzie. Zażarci wielbiciele tabów kłócą się z programistami wolącymi używać spacji. I tutaj rodzi się jeszcze jedna wątpliwość. Czy jeden tabulator powinien być równy czterem spacjom, ośmiu, czy może dwóm spacjom?

fragment serialu Sillicon Valley (produkcja HBO) S03E06

PEP 8 rozwiązuje ten spór na korzyść spacji. Wcięcie w kodzie musi być zrobione za pomocą 4 spacji na poziom wcięcia. Znak tabulacji \t odpowiada ośmiu spacjom, jednak w wersji Python3 nie można łączyć wcięć wykonanych tabulatorem i spacjami.
Na szczęście dla wielbicieli tabów, IDE takie jak PyCharm mogą automatycznie zamieniać znak tabulacji na 4 spacje.

Nazwy zmiennych

Python Software Foundation zdecydowała się na zastosowanie konwencji nazewniczej znanej jako snake_case. Oznacza to, że kilkuwyrazowe nazwy zmiennych i stałych należy rozdzielać podkreśleniem. Wyjątkiem od tego są nazwy klas, gdyż zapisujemy je stosując konwencję CamelCase.

Podczas nazywania zmiennych należy również pamiętać, aby nie stosować słów kluczowych języka ani nazw wbudowanych funkcji i obiektów.

import keyword
import builtins

keyword.kwlist  # lista słów kluczowych
dir(builtins)  # lista wbudowanych funkcji, obiektów oraz wyjątków

Docstrings

Docstrings (documentation strings) służą do tworzenia dokumentacji funkcji, klas oraz modułów napisanych w Pythonie. Konwencje ich używania opisane są w dokumencie PEP 257. Dokumentacje tego typu zapisujemy w potrójnym cudzysłowie na początku klasy lub funkcji:

class FooBar():
    """Opis klasy"""
    
    def foo():
        """Opis metody"""

    def bar():
        """
        Docstring może być również zapisany w kilku liniach.
        Zapisujemy go wtedy w ten sposób, zamykając string w nowej linii.
        """

Dobrze opisane klasy i funkcje są dużo łatwiejsze do opanowania dla innych programisów korzystających z naszego modułu. Można je odczytać np. za pomocą funkcji help.

foo_bar = FooBar()
help(foo_bar)
# wynik:
# Help on FooBar in module __main__ object:
# 
# class FooBar(builtins.object)
# |  Opis klasy
# |  
# |  Methods defined here:
# |  
# |  bar()
# |      Docstring może być również zapisany w kilku liniach.
# |      Zapisujemy go wtedy w ten sposób, zamykając string w nowej linii.
# |  
# |  foo()
# |      Opis metody

help(foo_bar.foo)
# wynik: Help on method foo in module __main__:
#
# foo(...) method of __main__.FooBar instance
#   Opis metody

Można również skorzystać z własności __doc__.

print(foo_bar.foo.__doc__)  # wynik: "Opis metody" 
print(foo_bar.__doc__)  # wynik: "Opis klasy"

Dokumentacja modułów

Docstrings służą również do opisywania modułów. Docstring opisujący moduł należy zamieścić na samym początku pliku z kodem.
Służą do tego również zmienne nazywane „dunders”, których nazwa rozpoczyna i kończy się podwójnym podkreśleniem.

"""awesome_module.py: Opis modułu"""

__name__ = "awesome_module"
__author__ = "Kamil Kwapisz"
__license__ = "MIT"
__version__ = "1.0"
__status__ = "production" 

# ... importy modułów i kod

Wprowadzenie nazwy modułu spowodowało również zmiany w tekście wypisywanym przez funkcję help:

help(foo_bar.foo)
# wynik: [...]
# foo(...) method of 

awesome_module

.FooBar instance
#    Opis metody

Formaty dokumentacji

Oczywiście utworzone zostały również formaty dokumentowania kodu mające na celu ujednolicenie powstających dokumentacji. Przedstawię poszczególne formaty docstring na przykładzie prostej funkcji dzielącej pierwszą liczbę całkowitą podaną w parametrach przed drugą liczbę całkowitą, która zwraca liczbę typu float.

  • reST(reStructuredText) – format zalecany przez dokument PEP 287.
    def divide(a: int, b: int) -> float:
    	"""
    	Funkcja dzieli liczbę a przez liczbę b
    
    	:param a: int dzielna 
    	:param b: int dzielnik 
    	:returns: wynik dzielenia a/b będący liczbą zmiennoprzecinkową
    	:raises ZeroDivisionError: zwraca wyjątek gdy b == 0
            :raises ValueError: zwraca wyjątek gdy podane parametry nie są liczbami
    	"""
  • Google
    def divide(a: int, b: int) -> float:
    	"""
    	Funkcja dzieli liczbę a przez liczbę b
    
    	Args:
    	    a (int): dzielna
    	    b (int): dzielnik
    
    	Returns:
    	    wynik dzielenia a/b będący liczbą zmiennoprzecinkową.
    
    	Raises:
    	    ZeroDivisionError: zwraca wyjątek gdy b == 0.
                ValueError: zwraca wyjątek gdy podane parametry nie są liczbami
    	"""
  • Numpydoc – format stosowany w bibliotece Numpy
    def divide(a: int, b: int) -> float:
    	"""
    	Funkcja dzieli liczbę a przez liczbę b
    
    	Parameters
    	----------
    	a : int
    	    dzielnik
    	b : int
    	    dzielna
    
    	Returns
    	-------
    	float
    	    wynik dzielenia a/b
    
    	Raises
    	------
    	ZeroDivisionError
    	    zwraca wyjątek gdy b == 0
            ValueError
                zwraca wyjątek gdy podane parametry nie są liczbami
    	"""
  • Epytext – format bazowany na javadoc
    def divide(a: int, b: int) -> float:
    	"""
    	Funkcja dzieli liczbę a przez liczbę b
    
    	@param a: int dzielna 
    	@param b: int dzielnik 
    	@return: float wynik dzielenia a/b
    	@raise ZeroDivisionError: zwraca wyjątek gdy b == 0
            @raise ValueError: zwraca wyjątek gdy podane parametry nie są liczbami
    	"""

Pamiętajcie, że najważniejsze aby w ogóle tworzyć dokumentację, styl jaki wybierzecie nie jest tak istotny. Dokumentacja powinna być przede wszystkim czytelna, a zapewniają to wszystkie wymienione wyżej formaty.

Podsumowanie

W tym poście chciałem jedynie zwrócić Waszą uwagę na zasady obowiązujące w Pythonowym community i na potrzebę ich respektowania. Opisanie wszystkich konwencji przedstawionych w dokumentach PEP 8 i PEP 257 można znaleźć na stronie pep8.org, do przejrzenia której bardzo zachęcam.
Stosowanie się do wprowadzonych zasad może wydawać się trudne, jednak z czasem wchodzi ono w nawyk. Szczególnie pomocne przy tym będzie korzystanie np. z PyCharm lub innego edytora skonfigurowanego tak, aby wskazywał nam miejsca niezgodne z konwencjami. Można tak nawet skonfigurować VIMa, o którym więcej możecie dowiedzieć się z mojego posta 9 funkcji VIMa, które powinieneś znać.

The Zen of Python

A na koniec kilka zasad z PEP 20, do których powinien stosować się każdy programista, niezależnie od technologii 😉

>>> import this  # mały easter egg :)
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!


Jeśli chcesz być na bieżąco z najnowszymi materiałami, polub nasz fanpage na Facebooku:

https://www.facebook.com/kamil.kwapisz.python
Tagged , , ,