1. Software Engineering Conventions

1.1. PEP 8 - Style Guide for Python Code

1.1.1. Tabs or spaces?

  • 4 spacje
  • IDE zamienia tab na 4 spacje

1.1.2. Line length

  • 79 znaków
  • soft wrap
  • co z monitorami 4k?
  • najbardziej kontrowersyjna klauzula

1.1.3. File encoding

  • UTF-8
  • always remember to open files for reading and writing with encoding='utf-8'

1.1.4. Comments

  • Better named functions and variables:

    def fabs(a, b):
        return float(abs(a + b))
    
    
    def float_absolute_value(a, b):
        return float(abs(a + b))
    
    
    def float_absolute_value(a: int, b: int) -> float:
        return float(abs(a + b))
    

1.1.5. Commented code?

  • NO!
  • Never commit files with commented code

1.1.6. Author name or revision version

  • Do not put author name or revision version to the files
  • Version Control System is responsible for that

1.1.7. Naming convention

1.1.7.1. Constants and Variables

  • Używanie _ w nazwach (snake_case) - // Python - snake ;)

  • variable or variable_name

    name = 'José Jiménez'
    
    first_name = 'José'
    last_name = 'Jiménez'
    
  • CONSTANT or CONSTANT_NAME

    PATH = '/etc/hosts'
    
    FILE_NAME = 'README.txt'
    

1.1.7.2. Classes

  • CamelCase

    class MyClass:
        pass
    

1.1.8. Methods/Functions

  • Używanie _ w nazwach (snake_case) - // Python - snake ;)

  • method_name() or function_name()

    def add_numbers(a, b):
        return a + b
    
  • Nie robimy camelCase

    def addNumbers(a, b):
        return a + b
    

1.1.9. Modules names

  • nazwymodulow

  • nazwy_modulow

  • Preferable one word

    import random
    import argparse
    

1.1.10. Function/Method argument names

  • self

    class Astronaut:
        name = 'José Jiménez'
    
        def say_hello(self):
            print(f'My name... {self.name}')
    
  • cls

    class Astronaut:
        pass
    
    class Cosmonaut:
        pass
    
    class Starman:
        pass
    
    def is_spaceman(cls):
        if instance(cls, (Astronaut, Cosmonaut)):
            return True
        else:
            return False
    
    
    is_spaceman(Cosmonaut)  # True
    is_spaceman(Astronaut)  # True
    is_spaceman(Starman)    # False
    
  • self and other

    class Vector:
        x = 0
        y = 1
    
        def __add__(self, other):
            return Vector(
                x=self.x+other.x,
                y=self.y+other.y
            )
    

1.1.11. Using __ and _ in names

  • W Pythonie nie ma private/protected/public

  • Funkcje o nazwie zaczynającej się od _ przez konwencję są traktowane jako prywatne

    from random import _ceil
    
    _ceil()
    # good IDE will display information, that you're accessing protected member
    
  • Funkcje i zmienne o nazwie zaczynającej się od __ i kończących się na __ przez konwencję są traktowane jako systemowe

    print(__file__)
    
  • _ at the end of name when name collision

    def print_(text1, text2):
        print(values, sep=';', end='\n')
    

1.1.12. Single or double quotes?

  • Python nie rozróżnia czy stosujemy pojedyncze znaki cudzysłowiu czy podwójne.
  • Ważne jest aby wybrać jedną konwencję i się jej konsekwentnie trzymać.
  • Interpreter Pythona domyślnie stosuje pojedyncze znaki cudzysłowia.
  • Z tego powodu w tej książce będziemy trzymać się powyższej konwencji.
  • Ma to znaczenie przy doctest, który zawsze korzysta z pojedynczych i rzuca errorem jak są podwójne
print('it\'s José\'s book')
print("it's José's book")
print('<a href="http://python.astrotech.io">Python and Machine Learning</a>')

1.1.13. Indents

Code Listing 1.6. Good
# More indentation included to distinguish this from the rest.
def server(
        host='localhost', port=443, secure=True,
        username='admin', password='admin'):
    return locals()


# Aligned with opening delimiter.
connection = server(host='localhost', port=443, secure=True,
                    username='admin', password='admin')

# Hanging indents should add a level.
connection = server(
    host='localhost', port=443, secure=True,
    username='admin', password='admin')

# The best
connection = server(
    host='localhost',
    username='admin',
    password='admin',
    port=443,
    secure=True,
)
Code Listing 1.7. Bad
# Further indentation required as indentation is not distinguishable.
def Connection(
    host='localhost', port=1337,
    username='admin', password='admin'):
    return host, port, username, password


# Arguments on first line forbidden when not using vertical alignment.
connection = Connection(host='localhost', port=1337,
    username='admin', password='admin')

1.1.14. Brackets

vector = [
    1, 2, 3,
    4, 5, 6,
]

result = some_function_that_takes_arguments(
    'a', 'b', 'c',
    'd', 'e', 'f',
)

vector = [
    1, 2, 3,
    4, 5, 6]

result = some_function_that_takes_arguments(
    'a', 'b', 'c',
    'd', 'e', 'f')

1.1.15. Line continuation

Linie możemy łamać poprzez stawianie znaku ukośnika \ na końcu:

with open('/path/to/some/file/you/want/to/read') as file1, \
        open('/path/to/some/file/being/written', mode='w') as file2:
    content = file1.read()
    file2.write(content)
class Server:
    def __init__(self, username, password, host='localhost'
                 port=80, secure=False):

        if not instance(username, str) or not instance(password, str) or
                not instance(host, str) or not instance(secure, bool) or
                (not instance(port, int) and 0 < port <= 65535):
            raise TypeError(f'One of your parameters is incorrect type')

     def __str__(self):
        if secure:
            protocol = 'https'
        else:
            protocol = 'http'

        return f'{protocol}://{self.username}:{self.password}@{self.host}:{self.port}/'

server = Server(
    host='localhost',
    username='admin',
    password='admin',
    port=443,
    secure=True,
)

1.1.16. Blank lines

class Server:
    def __init__(self, username, password, host='localhost'
                 port=80, secure=False):

        if not instance(username, str):
            raise TypeError(f'Username must be str')

        if not instance(password, str):
            raise TypeError(f'Password must be str')

        if not instance(port, int):
            raise TypeError(f'Port must be int')
        elif: 0 < port <= 65535
            raise ValueError(f'Port must be 0-65535')

    def __str__(self):
        if secure:
            protocol = 'https'
        else:
            protocol = 'http'

        return f'{protocol}://{self.username}:{self.password}@{self.host}:{self.port}/'

1.1.17. Whitespace in function calls

spam(ham[1], {eggs: 2})        # Good
spam( ham[ 1 ], { eggs: 2 } )  # Bad
spam(1)     # Good
spam (1)    # Bad
do_one()    # Good
do_two()    # Good
do_three()  # Good

do_one(); do_two(); do_three()                  # Bad

do_one(); do_two(); do_three(long, argument,    # Bad
                             list, like, this)  # Bad

1.1.18. Whitespace in slices

ham[1:9]                          # Good
ham[1:9:3]                        # Good
ham[:9:3]                         # Good
ham[1::3]                         # Good
ham[1:9:]                         # Good

ham[1: 9]                         # Bad
ham[1 :9]                         # Bad
ham[1:9 :3]                       # Bad
ham[lower:upper]                  # Good
ham[lower:upper:]                 # Good
ham[lower::step]                  # Good

ham[lower : : upper]              # Bad
ham[lower+offset : upper+offset]  # Good
ham[: upper_fn(x) : step_fn(x)]   # Good
ham[:: step_fn(x)]                # Good

ham[lower + offset:upper + offset]    # Bad
ham[:upper]             # Good
ham[ : upper]           # Bad
ham[ :upper]            # Bad

1.1.19. Whitespace in assignments

x = 1                   # Good
y = 2                   # Good
long_variable = 3       # Good

x             = 1       # Bad
y             = 2       # Bad
long_variable = 3       # Bad
i = i + 1               # Good
i=i+1                   # Bad
submitted += 1          # Good
submitted +=1           # Bad

1.1.20. Whitespace in math operators

x = x*2 - 1             # Good
x = x * 2 - 1           # Bad
hypot2 = x*x + y*y      # Good
hypot2 = x * x + y * y  # Bad
c = (a+b) * (a-b)      # Good
c = (a + b) * (a - b)  # Bad

1.1.21. Whitespace in accessors

dct['key'] = lst[index]     # Good
dct ['key'] = lst[ index ]  # Bad

1.1.22. Whitespace in functions

Good:
def complex(real, imag=0.0):
    return magic(r=real, i=imag)
Bad:
def complex(real, imag = 0.0):
    return magic(r = real, i = imag)

1.1.23. Whitespace in conditionals

Good:
if foo == 'blah':
    do_blah_thing()
Bad:
if foo == 'blah': do_blah_thing()

if foo == 'blah': one(); two(); three()

if foo == 'blah': do_blah_thing()
else: do_non_blah_thing()

1.1.24. Whitespace in exceptions

Good:
try:
    do_something()
except Exception:
    pass
Bad:
try: something()
finally: cleanup()

1.1.25. Conditionals

Good:
if greeting:
    pass
Bad:
if greeting == True:
    pass

if greeting is True:
    pass

1.1.26. Negative Conditionals

Good:
if name is not None:
    pass
Bad:
if not name is None:  # if (! name == null) {}
    pass
usernames = {'José', 'Max', 'Иван'}

# if (! usernames.contains('José')) {}
if not 'José' in usernames:
    print('I do not know you')
else:
    print('Hello my friend')

1.1.27. Checking if not empty

Good:
if sequence:
    pass

if not sequence:
    pass
Bad:
if len(sequence):
    pass

if not len(sequence):
    pass

1.1.28. Explicit return

Good:
def foo(x):
    if x >= 0:
        return math.sqrt(x)
    else:
        return None
Bad:
def foo(x):
    if x >= 0:
        return math.sqrt(x)

1.1.29. Explicit return value

Good:
def bar(x):
    if x < 0:
        return None
    return math.sqrt(x)
Bad:
def bar(x):
    if x < 0:
        return
    return math.sqrt(x)

1.1.30. Imports

  • Każdy z importów powinien być w osobnej linii
  • importy systemowe na górze
  • importy bibliotek zewnętrznych poniżej systemowych
  • importy własnych modułów poniżej bibliotek zewnętrznych
  • jeżeli jest dużo importów, pomiędzy grupami powinna być linia przerwy
Good:
import os
import sys
from random import shuffle
from subprocess import Popen
from subprocess import PIPE
import requests
import numpy as np
import os
import sys
from random import shuffle
from subprocess import Popen, PIPE
import requests
import numpy as np
Bad:
import sys, os, requests, numpy
import sys, os
import requests, numpy

1.1.31. Whitespace with type annotations

Good:
def function(first: str):
    pass

def function(first: str = None):
    pass

def function() -> None:
    pass

def function(first: str, second: str = None, limit: int = 1000) -> int:
    pass
Bad:
def function(first: str=None):
    pass

def function(first:str):
    pass

def function(first: str)->None:
    pass

1.2. PEP 20 - The Zen of Python

import this

1.2.1. Most important rules

1.2.1.1. English

  • Explicit is better than implicit.
  • Simple is better than complex.
  • Readability counts.
  • Special cases aren’t special enough to break the rules.
  • If the implementation is hard to explain, it’s a bad idea.

1.2.1.2. Polish

  • Wyrażone wprost jest lepsze niż domniemane.
  • Proste jest lepsze niż złożone.
  • Czytelność się liczy.
  • Sytuacje wyjątkowe nie są na tyle wyjątkowe, aby łamać reguły.
  • Jeśli rozwiązanie jest trudno wyjaśnić, to jest ono złym pomysłem.

1.2.2. Full Text

1.2.2.1. English

  • 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!

1.2.2.2. Polish

  • Piękne jest lepsze niż brzydkie.
  • Wyrażone wprost jest lepsze niż domniemane.
  • Proste jest lepsze niż złożone.
  • Złożone jest lepsze niż skomplikowane.
  • Płaskie jest lepsze niż wielopoziomowe.
  • Rzadkie jest lepsze niż gęste.
  • Czytelność się liczy.
  • Sytuacje wyjątkowe nie są na tyle wyjątkowe, aby łamać reguły.
  • Choć praktyczność przeważa nad konsekwencją.
  • Błędy zawsze powinny być sygnalizowane.
  • Chyba że zostaną celowo ukryte.
  • W razie niejasności powstrzymaj pokusę zgadywania.
  • Powinien być jeden – i najlepiej tylko jeden – oczywisty sposób na zrobienie danej rzeczy.
  • Choć ten sposób może nie być oczywisty jeśli nie jest się Holendrem.
  • Teraz jest lepsze niż nigdy.
  • Chociaż nigdy jest często lepsze niż natychmiast.
  • Jeśli rozwiązanie jest trudno wyjaśnić, to jest ono złym pomysłem.
  • Jeśli rozwiązanie jest łatwo wyjaśnić, to może ono być dobrym pomysłem.
  • Przestrzenie nazw to jeden z niesamowicie genialnych pomysłów – miejmy ich więcej!

1.3. Magic number i magic string

  • NO!

1.4. pycodestyle

  • Previously known as pep8
  • Python style guide checker.
  • pycodestyle is a tool to check your Python code against some of the style conventions in PEP 8
  • Plugin architecture: Adding new checks is easy
  • Parseable output: Jump to error location in your editor
  • Small: Just one Python file, requires only stdlib
  • Comes with a comprehensive test suite

1.4.1. Installation

pip install pycodestyle

1.4.2. Usage

pycodestyle FILENAME.py
pycodestyle DIRECTORY/*.py
pycodestyle DIRECTORY/
pycodestyle --statistics -qq DIRECTORY/
pycodestyle --show-source --show-pep8 FILENAME.py

1.4.3. Configuration

  • setup.cfg
[pycodestyle]
max-line-length = 120
ignore = E402,W391

1.5. Assignment

1.5.1. Cleanup your file

  1. Install pycodestyle
  2. Run pycodestyle on your last script
  3. Fix all errors
  4. Run pycodestyle on directory with all of your scripts
  5. Fix all errors
About:
  • Lines of code to write: 2 lines
  • Estimated time of completion: 5 min
Co zadanie sprawdza:
 
  • Umiejętność czytania komunikatów
  • Umiejętność pracy z terminalem
  • Utrzymywanie konwencji PEP8