Code-Smells and Anti-Patterns#
Functions that should be objects#
In addition to object-oriented programming, Python also supports procedural programming using functions and inheritable classes. Both paradigms should, however, be applied to the appropriate problems.
Typical symptoms of functional code that should be converted to classes are
similar arguments across functions
high number of distinct Halstead operands
mix of mutable and immutable functions
For example, three functions with ambiguous usage can be reorganised so, that
load_image() is replaced by
crop() becomes a class
get_thumbnail() a property:
class Image(object): thumbnail_resolution = 128 def __init__(self, path): ... def crop(self, width, height): ... @property def thumbnail(self): ... return thumb
Objects that should be functions#
Sometimes, however, object-oriented code should also be better broken down into
functions, for example if a class contains only one other method apart from
.__init__() or only static methods.
You do not have to search for such classes manually, but there is a pylint rule for it:
$ pipenv run pylint --disable=all --enable=R0903 requests ************* Module requests.auth requests/auth.py:72:0: R0903: Too few public methods (1/2) (too-few-public-methods) requests/auth.py:100:0: R0903: Too few public methods (1/2) (too-few-public-methods) ************* Module requests.models requests/models.py:60:0: R0903: Too few public methods (1/2) (too-few-public-methods) ----------------------------------- Your code has been rated at 9.99/10
This shows us that two classes with only one public method have been defined in
auth.py, in lines 72ff. and 100ff. Also in
models.py there is a class
with only one public method from line 60.
«Flat is better than nested.»
– Tim Peters, Zen of Python
Nested code makes it difficult to read and understand. You need to understand and remember the conditions as you go through the nestings. Objectively, the cyclomatic complexity increases as the number of code branches increases.
You can reduce nested methods with multiple nested
if statements by
replacing levels with methods that return
False if necessary. Then you can
.count() to check if the number of errors is
Another possibility is to use list comprehensions. This way the code
results =  for item in iterable: if item == match: results.append(item)
can be replaced by
results = [item for item in iterable if item == match]
The itertools of the Python standard library are often also good for reducing the nesting depth by creating functions to create iterators from data structures.
You can also filter with itertools, for example with filterfalse:
>>> from itertools import filterfalse >>> from math import isnan >>> from statistics import median >>> data = [20.7, float('NaN'),19.2, 18.3, float('NaN'), 14.4] >>> sorted(data) [20.7, nan, 14.4, 18.3, 19.2, nan] >>> median(data) 16.35 >>> sum(map(isnan, data)) 2 >>> clean = list(filterfalse(isnan, data)) >>> clean [20.7, 19.2, 18.3, 14.4] >>> sorted(clean) [14.4, 18.3, 19.2, 20.7] >>> median(clean) 18.75
Query tools for complex dicts#
Reduce code with
introduced in Python 3.7 and there is also a backport for Python 3.6. They are
meant to simplify the definition of classes that are mainly created to store
values and can then be accessed via attribute search. Some examples are
collections.namedtuple(), +:py:class:typing.NamedTuple, Recipes to
Records  and Nested Dicts . Data classes save you from writing and
managing these methods.
PEP 557 – Data Classes
attrs is a Python package that has been
around much longer than
dataclasses, is more comprehensive and can also be
used with older versions of Python.