2.1. Quality PEP-8
2.1.1. PEP 8 - Style Guide for Python Code
PEP8 song https://youtu.be/hgI0p1zf31k
2.1.2. Tabs or spaces?
4 spacje
IDE zamienia tab na 4 spacje
2.1.3. Line length
najbardziej kontrowersyjna klauzula
79 znaków kod
72 znaki docstrings/comments
Python standard library is conservative and requires limiting lines to 79 characters (and docstrings/comments to 72)
soft wrap
co z monitorami 4k?
Preferred way of wrapping long lines is by using Python's implied line continuation inside parentheses, brackets and braces.
class FoodProduct(models.Model):
vitamins_folic_acid = models.DecimalField(verbose_name=_('Folic Acid'), help_text=_('µg/100g'), decimal_places=2, max_digits=5, blank=True, null=True, default=None)
vitamins_a = models.DecimalField(verbose_name=_('Vitamin A'), help_text=_('µg/100g'), decimal_places=2, max_digits=5, blank=True, null=True, default=None)
vitamins_b1 = models.DecimalField(verbose_name=_('Vitamin B1'), help_text=_('mg/100g'), decimal_places=2, max_digits=5, blank=True, null=True, default=None)
vitamins_b2 = models.DecimalField(verbose_name=_('Vitamin B2'), help_text=_('mg/100g'), decimal_places=2, max_digits=5, blank=True, null=True, default=None)
vitamins_b6 = models.DecimalField(verbose_name=_('Vitamin B6'), help_text=_('mg/100g'), decimal_places=2, max_digits=5, blank=True, null=True, default=None)
vitamins_b12 = models.DecimalField(verbose_name=_('Vitamin B12'), help_text=_('µg/100g'), decimal_places=2, max_digits=5, blank=True, null=True, default=None)
vitamins_c = models.DecimalField(verbose_name=_('Vitamin C'), help_text=_('mg/100g'), decimal_places=2, max_digits=5, blank=True, null=True, default=None)
vitamins_d = models.DecimalField(verbose_name=_('Vitamin D'), help_text=_('µg/100g'), decimal_places=2, max_digits=5, blank=True, null=True, default=None)
vitamins_e = models.DecimalField(verbose_name=_('Vitamin E'), help_text=_('mg/100g'), decimal_places=2, max_digits=5, blank=True, null=True, default=None)
vitamins_pp = models.DecimalField(verbose_name=_('Vitamin PP'), help_text=_('mg/100g'), decimal_places=2, max_digits=5, blank=True, null=True, default=None)
minerals_zinc = models.DecimalField(verbose_name=_('Zinc'), help_text=_('mg/100g'), decimal_places=2, max_digits=5, blank=True, null=True, default=None)
minerals_phosphorus = models.DecimalField(verbose_name=_('Phosphorus'), help_text=_('mg/100g'), decimal_places=2, max_digits=5, blank=True, null=True, default=None)
minerals_iodine = models.DecimalField(verbose_name=_('Iodine'), help_text=_('µg/100g'), decimal_places=2, max_digits=5, blank=True, null=True, default=None)
minerals_magnesium = models.DecimalField(verbose_name=_('Magnesium'), help_text=_('mg/100g'), decimal_places=2, max_digits=5, blank=True, null=True, default=None)
minerals_copper = models.DecimalField(verbose_name=_('Copper'), help_text=_('mg/100g'), decimal_places=2, max_digits=5, blank=True, null=True, default=None)
minerals_potasium = models.DecimalField(verbose_name=_('Potasium'), help_text=_('mg/100g'), decimal_places=2, max_digits=5, blank=True, null=True, default=None)
minerals_selenium = models.DecimalField(verbose_name=_('Selenium'), help_text=_('µg/100g'), decimal_places=2, max_digits=5, blank=True, null=True, default=None)
minerals_sodium = models.DecimalField(verbose_name=_('Sodium'), help_text=_('mg/100g'), decimal_places=2, max_digits=5, blank=True, null=True, default=None)
minerals_calcium = models.DecimalField(verbose_name=_('Calcium'), help_text=_('mg/100g'), decimal_places=2, max_digits=5, blank=True, null=True, default=None)
minerals_iron = models.DecimalField(verbose_name=_('Iron'), help_text=_('mg/100g'), decimal_places=2, max_digits=5, blank=True, null=True, default=None)
2.1.4. File encoding
UTF-8
always remember to open files for reading and writing with
encoding='utf-8'
All identifiers in the Python standard library MUST use ASCII-only identifiers, and SHOULD use English words wherever feasible (in many cases, abbreviations and technical terms are used which aren't English).
String literals and comments must also be in ASCII.
Authors whose names are not based on the Latin alphabet (latin-1, ISO/IEC 8859-1 character set) MUST provide a transliteration of their names in this character set.
2.1.6. Documentation Strings
PEP 257 -- Docstring Conventions
Write docstrings for all public modules, functions, classes, and methods.
Docstrings are not necessary for non-public methods, but you should have a comment that describes what the method does.
For one liner docstrings, please keep the closing """ on the same line.
PEP 257 -- Docstring Conventions: For multiline
str
always use three double quote ("""
) characters
2.1.7. Use better names, rather than comments
def cal_var(data):
"""Calculate variance"""
return sum((Xi-m) ** 2 for Xi in data) / len(data)
def calculate_variance(data):
return sum((Xi-m) ** 2 for Xi in data) / len(data)
def fabs(a, b):
return float(abs(a + b))
def float_absolute_value(a, b):
return float(abs(a + b))
def abs(a: int, b: int) -> float:
return float(abs(a + b))
def absolute_value(a: int, b: int) -> float:
return float(abs(a + b))
2.1.8. Commented code?
NO!
Never commit files with commented code
2.1.10. Naming convention
Constants and Variables:
Używanie
_
w nazwach (snake_case) - // Python - snake ;)
variable
orvariable_name
name = 'José Jiménez' firstname = 'José' lastname = 'Jiménez'
CONSTANT
orCONSTANT_NAME
PATH = '/etc/hosts' FILE_NAME = 'README.txt'
Classes:
PascalCase
class MyClass: pass
2.1.11. Class Attributes
Public attributes should have no leading underscores.
If your public attribute name collides with a reserved keyword, append a single trailing underscore to your attribute name.
cls
is the preferred spelling for any variable or argument which is known to be a class, especially the first argument to a class method.
2.1.12. Methods/Functions
Używanie
_
w nazwach (snake_case) - // Python - snake ;)method_name()
orfunction_name()
def add_numbers(a, b): return a + b
Nie robimy camelCase
def addNumbers(a, b): return a + b
2.1.13. Modules names
modulename
module_name
Preferable one word
import random import argparse
2.1.14. 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
andother
class Vector: x = 0 y = 1 def __add__(self, other): return Vector( x=self.x+other.x, y=self.y+other.y )
2.1.15. Using __
and _
in names
W Pythonie nie ma private/protected/public
Funkcje o nazwie zaczynającej się od
_
przez konwencję są traktowane jako prywatnefrom 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 systemoweprint(__file__)
_
at the end of name when name collisiondef print_(text1, text2): print(values, sep=';', end='\n')
2.1.16. Single or double quotes?
Python nie rozróżnia czy stosujemy pojedyncze znaki cudzysłowu czy podwójne.
Ważne jest aby wybrać jedną konwencję i się jej konsekwentnie trzymać.
Interpreter Pythona domyślnie stosuje pojedyncze znaki cudzysłowu.
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ójnePEP 257 -- Docstring Conventions: For multiline
str
always use three double quote ("""
) characters
print('It\'s Watney\'s Mars')
print("It is Watney's Mars")
print('<a href="https://python3.info">Python and Machine Learning</a>')
2.1.17. Trailing Commas
Yes:
FILES = ('setup.cfg',)
OK, but confusing:
FILES = 'setup.cfg',
2.1.18. Indents
Good:
# More indentation included to distinguish this from the rest.
def server(
host='example.com', port=443, secure=True,
username='myusername', password='mypassword'):
return locals()
# Aligned with opening delimiter.
connection = server(host='example.com', port=443, secure=True,
username='myusername', password='mypassword')
# Hanging indents should add a level.
connection = server(
host='example.com', port=443, secure=True,
username='myusername', password='mypassword')
# The best
connection = server(
host='example.com',
username='myusername',
password='mypassword',
port=443,
secure=True,
)
Bad:
# Further indentation required as indentation is not distinguishable.
def Connection(
host='example.com', port=1337,
username='myusername', password='mypassword'):
return host, port, username, password
# Arguments on first line forbidden when not using vertical alignment.
connection = Connection(host='example.com', port=1337,
username='myusername', password='mypassword')
2.1.19. 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')
TYPE_CHOICES = [
('custom', _('Custom Made')),
('brand', _('Brand Product')),
('gourmet', _('Gourmet Food')),
('restaurant', _('Restaurant'))]
FORM_CHOICES = [
('solid', _('Solid')),
('liquid', _('Liquid'))]
CATEGORY_CHOICES = [
('other', _('Other')),
('fruits', _('Fruits')),
('vegetables', _('Vegetables')),
('meat', _('Meat'))]
2.1.20. Modules
Modules should explicitly declare the names in their public API using the
__all__
attribute.Setting
__all__
to an empty list indicates that the module has no public API.
2.1.21. 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)
Easy to match operators with operands:
income = (gross_wages
+ taxable_interest
+ (dividends - qualified_dividends)
- ira_deduction
- student_loan_interest)
class Server:
def __init__(self, username, password, host='example.com'
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='example.com',
username='myusername',
password='mypassword',
port=443,
secure=True,
)
2.1.22. Blank lines
Surround top-level function and class definitions with two blank lines.
Method definitions inside a class are surrounded by a single blank line.
Extra blank lines may be used (sparingly) to separate groups of related functions.
Use blank lines in functions, sparingly, to indicate logical sections.
class Server:
def __init__(self, username, password, host='example.com'
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}/'
2.1.23. 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
2.1.24. 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
2.1.25. 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
2.1.26. 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
2.1.27. Whitespace in accessors
dct['key'] = lst[index] # Good
dct ['key'] = lst[ index ] # Bad
2.1.28. 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)
- Controversial:
def move(self, left: int = 0, down: int = 0, up: int = 0, right: int = 0) -> None: self.set_position_coordinates( x=self.position.x + right - left, y=self.position.y + down - up )
2.1.29. 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()
2.1.30. Whitespace in exceptions
- Good:
try: do_something() except Exception: pass
- Bad:
try: something() finally: cleanup()
2.1.31. Conditionals
- Good:
if greeting: pass
- Bad:
if greeting == True: pass if greeting is True: pass
2.1.32. Negative Conditionals
- Good:
if name is not None: pass
- Bad:
# if (! name == null) {} if not name is None: pass
usernames = {'mwatney', 'mlewis', 'rmartinez'} # if (! usernames.contains('José')) {} if not 'mwatney' in usernames: print('I do not know you') else: print('Hello my friend')
2.1.33. Checking if not empty
- Good:
if sequence: pass if not sequence: pass
- Bad:
if len(sequence): pass if not len(sequence): pass
2.1.34. 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)
2.1.35. 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)
2.1.36. 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 import requests import numpy as np
from datetime import date from datetime import time from datetime import datetime from datetime import timezone
from datetime import date, time, datetime, timezone
from datetime import date, time, datetime, timezone 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
2.1.37. 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
2.1.38. Magic number i magic string
NO!
2.1.39. PEP 8, but...
2.1.40. Static Code Analysis
More information in cicd-tools
More information in cicd-pipelines
2.1.41. pycodestyle
Previously known as
pep8
Python style guide checker.
pycodestyle
is a tool to check your Python code against some of the style conventions inPEP 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
Installation:
$ pip install pycodestyle
Usage:
$ pycodestyle FILE.py $ pycodestyle DIRECTORY/*.py $ pycodestyle DIRECTORY/ $ pycodestyle --statistics -qq DIRECTORY/ $ pycodestyle --show-source --show-pep8 FILE.py
Configuration:
setup.cfg
[pycodestyle] max-line-length = 120 ignore = E402,W391
2.1.42. Assignments
# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author
# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -v myfile.py`
# %% About
# - Name: DevOps PEP8 Pycodestyle
# - Difficulty: easy
# - Lines: 2
# - Minutes: 5
# %% English
# 1. Install `pycodestyle`
# 2. Run `pycodestyle` on your last script
# 3. Fix all errors
# 4. Run doctests - all must succeed
# %% Polish
# 1. Install `pycodestyle`
# 2. Uruchom `pycodestyle` na swoim ostatnim skrypcie
# 3. Popraw wszystkie błędy
# 4. Uruchom doctesty - wszystkie muszą się powieść
2.1.5. Comments
Comments that contradict the code are worse than no comments.
Comments should be complete sentences.
Block comments generally consist of one or more paragraphs built out of complete sentences
Each sentence ending in a period.
Python coders from non-English speaking countries: please write your comments in English, unless you are 120% sure that the code will never be read by people who don't speak your language.
Each line of a block comment starts with a # and a single space (unless it is indented text inside the comment).