2.1. Software Engineering Conventions¶
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://python.astrotech.io">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¶
"""
* Assignment: DevOps PEP8 Pycodestyle
* Complexity: easy
* Lines of code: 2 lines
* Time: 5 min
English:
TODO: English Translation
X. Run doctests - all must succeed
Polish:
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
6. 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).