Clean Code in Python
Clean Code in Python
EuroPython
July 2016 - Bilbao, Spain
Mariano Anaya
/me
● Python developer
● Interests
○ Linux
○ Software development
○ Software Architecture / system design
“You know you are working on clean code when each routine
you read turns out to be pretty much what you expected.
You can call it beautiful code when the code also makes it
look like the language was made for the problem.”
Ward Cunningham
...Technical Debt
Meaning
def elapse(year):
days = 365
if year % 4 == 0 or (year % 100 == 0 and year % 400 == 0):
days += 1
for day in range(1, days + 1):
print("Day {} of {}".format(day, year))
Meaning and logic separation
def elapse(year):
days = 365
if year % 4 == 0 or (year % 100 == 0 and year % 400 == 0): ?
days += 1
for day in range(1, days + 1):
print("Day {} of {}".format(day, year))
def elapse(year):
days = 365 def is_leap(year):
if is_leap(year):
days += 1 ...
...
Duplicated code
Problems:
def decorator(original_function):
def inner(*args, **kwargs):
# modify original function, or add extra logic
return original_function(*args, **kwargs)
return inner
def update_db_indexes(cursor):
commands = (
"""REINDEX DATABASE transactional""",
)
try:
for command in commands:
cursor.execute(command)
except Exception as e:
logger.exception("Error in update_db_indexes: %s", e)
return -1
else:
logger.info("update_db_indexes run successfully")
return 0
def move_data_archives(cursor):
commands = (
"""INSERT INTO archive_orders SELECT * from orders
WHERE order_date < '2016-01-01' """,
"""DELETE from orders WHERE order_date < '2016-01-01'
""",)
try:
for command in commands:
cursor.execute(command)
except Exception as e:
logger.exception("Error in move_data_archives: %s", e)
return -1
else:
logger.info("move_data_archives run successfully")
return 0
def db_status_handler(db_script_function):
def inner(cursor):
commands = db_script_function(cursor)
function_name = db_script_function.__qualname__
try:
for command in commands:
cursor.execute(command)
except Exception as e:
logger.exception("Error in %s: %s", function_name, e)
return -1
else:
logger.info("%s run successfully", function_name)
return 0
return inner
@db_status_handler
def update_db_indexes(cursor):
return (
"""REINDEX DATABASE transactional""",
)
@db_status_handler
def move_data_archives(cursor):
return (
"""INSERT INTO archive_orders SELECT * from orders
WHERE order_date < '2016-01-01' """,
"""DELETE from orders WHERE order_date < '2016-01-01'
""",
)
Implementation details
. . .
player_status = PlayerStatus()
player_status.accumulate_points(20)
class PlayerStatus:
...
def accumulate_points(self, new_points):
current_score = int(self.redis_connection.get(self.key) or 0)
score = current_score + new_points
self.redis_connection.set(self.key, score)
. . .
-- implementation details
-- business logic
The kind of access I’d like to have
player_status.accumulate_points(20)
player_status.points += 20
...
print(player_status.points)
player_status.points = 100
How to achieve it
class PlayerStatus:
@property
def points(self):
return int(self.redis_connection.get(self.key) or 0)
@points.setter
def points(self, new_points):
self.redis_connection.set(self.key, new_points)
@property
class Stock:
def __init__(self, categories=None):
self.categories = categories or []
self._products_by_category = {}
def request_product_for_customer(customer, product, current_stock):
product_available_in_stock = False
for category in current_stock.categories:
for prod in category.products:
if prod.count > 0 and prod.id == product.id:
product_available_in_stock = True
if product_available_in_stock:
requested_product = current_stock.request(product)
customer.assign_product(requested_product)
else:
return "Product not available"
def request_product_for_customer(customer, product, current_stock):
product_available_in_stock = False
for category in current_stock.categories:
for prod in category.products:
if prod.count > 0 and prod.id == product.id:
product_available_in_stock = True
if product_available_in_stock:
requested_product = current_stock.request(product)
customer.assign_product(requested_product)
else:
return "Product not available"
Python was made for the problem
product in current_stock
Translates into:
current_stock.__contains__(product)
Looking for elements
class Stock:
...
def __contains__(self, product):
self.products_by_category()
available = self.categories.get(product.category)
...
Maintaining state
if product in current_stock:
Python’s mine
● PEP 8
○ Define coding guidelines for the project
○ Check automatically (as part of the CI)
● Docstrings (PEP 257)/ Function Annotations (PEP 3107)
● Unit tests
● Tools
○ Pycodestyle, Flake8, pylint, radon
○ coala
More info