Code Style¶

If you ask Python programmers what they like most about Python, they willoften cite its high readability. Indeed, a high level of readabilityis at the heart of the design of the Python language, following therecognized fact that code is read much more often than it is written.
One reason for the high readability of Python code is its relativelycomplete set of Code Style guidelines and “Pythonic” idioms.
When a veteran Python developer (a Pythonista) calls portions ofcode not “Pythonic”, they usually mean that these linesof code do not follow the common guidelines and fail to express its intent inwhat is considered the best (hear: most readable) way.
On some border cases, no best way has been agreed upon on how to expressan intent in Python code, but these cases are rare.
General concepts¶
Explicit code¶
While any kind of black magic is possible with Python, themost explicit and straightforward manner is preferred.
Bad
defmake_complex(*args):x,y=argsreturndict(**locals())
Good
defmake_complex(x,y):return{'x':x,'y':y}
In the good code above, x and y are explicitly received fromthe caller, and an explicit dictionary is returned. The developerusing this function knows exactly what to do by reading thefirst and last lines, which is not the case with the bad example.
One statement per line¶
While some compound statements such as list comprehensions areallowed and appreciated for their brevity and their expressiveness,it is bad practice to have two disjointed statements on the same line of code.
Bad
print('one');print('two')ifx==1:print('one')if<complexcomparison>and<othercomplexcomparison>:# do something
Good
print('one')print('two')ifx==1:print('one')cond1=<complexcomparison>cond2=<othercomplexcomparison>ifcond1andcond2:# do something
Function arguments¶
Arguments can be passed to functions in four different ways.
- Positional arguments are mandatory and have no default values. They arethe simplest form of arguments and they can be used for the few functionarguments that are fully part of the function’s meaning and their order isnatural. For instance, in
send(message,recipient)
orpoint(x,y)
the user of the function has no difficulty remembering that those twofunctions require two arguments, and in which order.
In those two cases, it is possible to use argument names when calling thefunctions and, doing so, it is possible to switch the order of arguments,calling for instancesend(recipient='World',message='Hello')
andpoint(y=2,x=1)
but this reduces readability and is unnecessarily verbose,compared to the more straightforward calls tosend('Hello','World')
andpoint(1,2)
.
- Keyword arguments are not mandatory and have default values. They areoften used for optional parameters sent to the function. When a function hasmore than two or three positional parameters, its signature is more difficultto remember and using keyword arguments with default values is helpful. Forinstance, a more complete
send
function could be defined assend(message,to,cc=None,bcc=None)
. Herecc
andbcc
areoptional, and evaluate toNone
when they are not passed another value.
Calling a function with keyword arguments can be done in multiple ways inPython; for example, it is possible to follow the order of arguments in thedefinition without explicitly naming the arguments, like insend('Hello','World','Cthulhu','God')
, sending a blind carbon copy toGod. It would also be possible to name arguments in another order, like insend('Helloagain','World',bcc='God',cc='Cthulhu')
. Those twopossibilities are better avoided without any strong reason to not follow thesyntax that is the closest to the function definition:send('Hello','World',cc='Cthulhu',bcc='God')
.
As a side note, following theYAGNIprinciple, it is often harder to remove an optional argument (and its logicinside the function) that was added “just in case” and is seemingly never used,than to add a new optional argument and its logic when needed.
- Thearbitrary argument list is the third way to pass arguments to afunction. If the function intention is better expressed by a signature withan extensible number of positional arguments, it can be defined with the
*args
constructs. In the function body,args
will be a tuple of allthe remaining positional arguments. For example,send(message,*args)
can be called with each recipient as an argument:send('Hello','God','Mom','Cthulhu')
, and in the function bodyargs
will be equal to('God','Mom','Cthulhu')
.
However, this construct has some drawbacks and should be used with caution. If afunction receives a list of arguments of the same nature, it is often moreclear to define it as a function of one argument, that argument being a list orany sequence. Here, ifsend
has multiple recipients, it is better to defineit explicitly:send(message,recipients)
and call it withsend('Hello',['God','Mom','Cthulhu'])
. This way, the user of the function can manipulatethe recipient list as a list beforehand, and it opens the possibility to passany sequence, including iterators, that cannot be unpacked as other sequences.
- Thearbitrary keyword argument dictionary is the last way to passarguments to functions. If the function requires an undetermined series ofnamed arguments, it is possible to use the
**kwargs
construct. In thefunction body,kwargs
will be a dictionary of all the passed namedarguments that have not been caught by other keyword arguments in thefunction signature.
The same caution as in the case ofarbitrary argument list is necessary, forsimilar reasons: these powerful techniques are to be used when there is aproven necessity to use them, and they should not be used if the simpler andclearer construct is sufficient to express the function’s intention.
It is up to the programmer writing the function to determine which argumentsare positional arguments and which are optional keyword arguments, and todecide whether to use the advanced techniques of arbitrary argument passing. Ifthe advice above is followed wisely, it is possible and enjoyable to writePython functions that are:
- easy to read (the name and arguments need no explanations)
- easy to change (adding a new keyword argument does not break other parts ofthe code)
Avoid the magical wand¶
A powerful tool for hackers, Python comes with a very rich set of hooks andtools allowing you to do almost any kind of tricky tricks. For instance, it ispossible to do each of the following:
- change how objects are created and instantiated
- change how the Python interpreter imports modules
- It is even possible (and recommended if needed) to embed C routines in Python.
However, all these options have many drawbacks and it is always better to usethe most straightforward way to achieve your goal. The main drawback is thatreadability suffers greatly when using these constructs. Many code analysistools, such as pylint or pyflakes, will be unable to parse this “magic” code.
We consider that a Python developer should know about these nearly infinitepossibilities, because it instills confidence that no impassable problem willbe on the way. However, knowing how and particularly whennot to usethem is very important.
Like a kung fu master, a Pythonista knows how to kill with a single finger, andnever to actually do it.
We are all responsible users¶
As seen above, Python allows many tricks, and some of them are potentiallydangerous. A good example is that any client code can override an object’sproperties and methods: there is no “private” keyword in Python. Thisphilosophy, very different from highly defensive languages like Java, whichgive a lot of mechanisms to prevent any misuse, is expressed by the saying: “Weare all responsible users”.
This doesn’t mean that, for example, no properties are considered private, andthat no proper encapsulation is possible in Python. Rather, instead of relyingon concrete walls erected by the developers between their code and others’, thePython community prefers to rely on a set of conventions indicating that theseelements should not be accessed directly.
The main convention for private properties and implementation details is toprefix all “internals” with an underscore. If the client code breaks this ruleand accesses these marked elements, any misbehavior or problems encountered ifthe code is modified is the responsibility of the client code.
Using this convention generously is encouraged: any method or property that isnot intended to be used by client code should be prefixed with an underscore.This will guarantee a better separation of duties and easier modification ofexisting code; it will always be possible to publicize a private property,but making a public property private might be a much harder operation.
Returning values¶
When a function grows in complexity, it is not uncommon to use multiple returnstatements inside the function’s body. However, in order to keep a clear intentand a sustainable readability level, it is preferable to avoid returningmeaningful values from many output points in the body.
There are two main cases for returning values in a function: the result of thefunction return when it has been processed normally, and the error cases thatindicate a wrong input parameter or any other reason for the function to not beable to complete its computation or task.
If you do not wish to raise exceptions for the second case, then returning avalue, such as None or False, indicating that the function could not performcorrectly might be needed. In this case, it is better to return as early as theincorrect context has been detected. It will help to flatten the structure ofthe function: all the code after the return-because-of-error statement canassume the condition is met to further compute the function’s main result.Having multiple such return statements is often necessary.
However, when a function has multiple main exit points for its normal course,it becomes difficult to debug the returned result, so it may be preferable tokeep a single exit point. This will also help factoring out some code paths,and the multiple exit points are a probable indication that such a refactoringis needed.
defcomplex_function(a,b,c):ifnota:returnNone# Raising an exception might be betterifnotb:returnNone# Raising an exception might be better# Some complex code trying to compute x from a, b and c# Resist temptation to return x if succeededifnotx:# Some Plan-B computation of xreturnx# One single exit point for the returned value x will help# when maintaining the code.
Idioms¶
A programming idiom, put simply, is away to write code. The notion ofprogramming idioms is discussed amply atc2and atStack Overflow.
Idiomatic Python code is often referred to as beingPythonic.
Although there usually is one — and preferably only one — obvious way to doit;the way to write idiomatic Python code can be non-obvious to Pythonbeginners. So, good idioms must be consciously acquired.
Some common Python idioms follow:
Unpacking¶
If you know the length of a list or tuple, you can assign names to itselements with unpacking. For example, sinceenumerate()
will providea tuple of two elements for each item in list:
forindex,iteminenumerate(some_list):# do something with index and item
You can use this to swap variables as well:
a,b=b,a
Nested unpacking works too:
a,(b,c)=1,(2,3)
In Python 3, a new method of extended unpacking was introduced byPEP 3132:
a,*rest=[1,2,3]# a = 1, rest = [2, 3]a,*middle,c=[1,2,3,4]# a = 1, middle = [2, 3], c = 4
Create an ignored variable¶
If you need to assign something (for instance, inUnpacking) butwill not need that variable, use__
:
filename='foobar.txt'basename,__,ext=filename.rpartition('.')
Note
Many Python style guides recommend the use of a single underscore “_
”for throwaway variables rather than the double underscore “__
”recommended here. The issue is that “_
” is commonly used as an aliasfor thegettext()
function, and is also used at theinteractive prompt to hold the value of the last operation. Using adouble underscore instead is just as clear and almost as convenient,and eliminates the risk of accidentally interfering with either ofthese other use cases.
Create a length-N list of lists¶
Because lists are mutable, the*
operator (as above) will create a listof N references to thesame list, which is not likely what you want.Instead, use a list comprehension:
four_lists=[[]for__inrange(4)]
Create a string from a list¶
A common idiom for creating strings is to usestr.join()
on an emptystring.
letters=['s','p','a','m']word=''.join(letters)
This will set the value of the variableword to ‘spam’. This idiom can beapplied to lists and tuples.
Searching for an item in a collection¶
Sometimes we need to search through a collection of things. Let’s look at twooptions: lists and sets.
Take the following code for example:
s=set(['s','p','a','m'])l=['s','p','a','m']deflookup_set(s):return's'insdeflookup_list(l):return's'inl
Even though both functions look identical, becauselookup_set is utilizingthe fact that sets in Python are hashtables, the lookup performancebetween the two is very different. To determine whether an item is in a list,Python will have to go through each item until it finds a matching item.This is time consuming, especially for long lists. In a set, on the otherhand, the hash of the item will tell Python where in the set to look fora matching item. As a result, the search can be done quickly, even if theset is large. Searching in dictionaries works the same way. Formore information see thisStackOverflowpage. For detailed information on the amount of time various common operationstake on each of these data structures, seethis page.
Because of these differences in performance, it is often a good idea to usesets or dictionaries instead of lists in cases where:
- The collection will contain a large number of items
- You will be repeatedly searching for items in the collection
- You do not have duplicate items.
For small collections, or collections which you will not frequently besearching through, the additional time and memory required to set up thehashtable will often be greater than the time saved by the improved searchspeed.
Zen of Python¶
Also known asPEP 20, the guiding principles for Python’s design.
>>>importthisThe Zen of Python, by Tim PetersBeautiful 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!
For some examples of good Python style, seethese slides from a Python usergroup.
PEP 8¶
PEP 8 is the de facto code style guide for Python. A high quality,easy-to-read version of PEP 8 is also available atpep8.org.
This is highly recommended reading. The entire Python community does theirbest to adhere to the guidelines laid out within this document. Some projectmay sway from it from time to time, while others may amend its recommendations.
That being said, conforming your Python code to PEP 8 is generally a good ideaand helps make code more consistent when working on projects with otherdevelopers. There is a command-line program,pycodestyle(previously known aspep8
), that can check your code for conformance.Install it by running the following command in your terminal:
$ pip install pycodestyle
Then run it on a file or series of files to get a report of any violations.
$ pycodestyle optparse.pyoptparse.py:69:11: E401 multiple imports on one lineoptparse.py:77:1: E302 expected 2 blank lines, found 1optparse.py:88:5: E301 expected 1 blank line, found 0optparse.py:222:34: W602 deprecated form of raising exceptionoptparse.py:347:31: E211 whitespace before '('optparse.py:357:17: E201 whitespace after '{'optparse.py:472:29: E221 multiple spaces before operatoroptparse.py:544:21: W601 .has_key() is deprecated, use 'in'
Auto-Formatting¶
There are several auto-formatting tools that can reformat your code,in order to comply with PEP 8.
autopep8
The programautopep8 can be used toautomatically reformat code in the PEP 8 style. Install the program with:
$ pip install autopep8
Use it to format a file in-place with:
$ autopep8 --in-place optparse.py
Excluding the--in-place
flag will cause the program to output the modifiedcode directly to the console for review. The--aggressive
flag will performmore substantial changes and can be applied multiple times for greater effect.
yapf
While autopep8 focuses on solving the PEP 8 violations,yapftries to improve the format of your code aside from complying with PEP 8.This formatter aims at providing as good looking code as a programmer whowrites PEP 8 compliant code.It gets installed with:
$ pip install yapf
Run the auto-formatting of a file with:
$ yapf --in-place optparse.py
Similar to autopep8, running the command without the--in-place
flag willoutput the diff for review before applying the changes.
black
The auto-formatterblack offers anopinionated and deterministic reformatting of your code base.Its main focus lies in providing a uniform code style without the need ofconfiguration throughout its users. Hence, users of black are able to forgetabout formatting altogether. Also, due to the deterministic approach minimalgit diffs with only the relevant changes are guaranteed. You can install thetool as follows:
$ pip install black
A python file can be formatted with:
$ black optparse.py
Adding the--diff
flag provides the code modification for review withoutdirect application.
Conventions¶
Here are some conventions you should follow to make your code easier to read.
Check if a variable equals a constant¶
You don’t need to explicitly compare a value to True, or None, or 0 – you canjust add it to the if statement. SeeTruth Value Testing for alist of what is considered false.
Bad:
ifattr==True:print('True!')ifattr==None:print('attr is None!')
Good:
# Just check the valueifattr:print('attr is truthy!')# or check for the oppositeifnotattr:print('attr is falsey!')# or, since None is considered false, explicitly check for itifattrisNone:print('attr is None!')
Access a Dictionary Element¶
Don’t use thedict.has_key()
method. Instead, usexind
syntax,or pass a default argument todict.get()
.
Bad:
d={'hello':'world'}ifd.has_key('hello'):print(d['hello'])# prints 'world'else:print('default_value')
Good:
d={'hello':'world'}print(d.get('hello','default_value'))# prints 'world'print(d.get('thingy','default_value'))# prints 'default_value'# Or:if'hello'ind:print(d['hello'])
Short Ways to Manipulate Lists¶
List comprehensionsprovides a powerful, concise way to work with lists.
Generator expressionsfollows almost the same syntax as list comprehensions but return a generatorinstead of a list.
Creating a new list requires more work and uses more memory. If you are just goingto loop through the new list, prefer using an iterator instead.
Bad:
# needlessly allocates a list of all (gpa, name) entires in memoryvaledictorian=max([(student.gpa,student.name)forstudentingraduates])
Good:
valedictorian=max((student.gpa,student.name)forstudentingraduates)
Use list comprehensions when you really need to create a second list, forexample if you need to use the result multiple times.
If your logic is too complicated for a short list comprehension or generatorexpression, consider using a generator function instead of returning a list.
Good:
defmake_batches(items,batch_size):""" >>> list(make_batches([1, 2, 3, 4, 5], batch_size=3)) [[1, 2, 3], [4, 5]] """current_batch=[]foriteminitems:current_batch.append(item)iflen(current_batch)==batch_size:yieldcurrent_batchcurrent_batch=[]yieldcurrent_batch
Never use a list comprehension just for its side effects.
Bad:
[print(x)forxinsequence]
Good:
forxinsequence:print(x)
Filtering a list¶
Bad:
Never remove items from a list while you are iterating through it.
# Filter elements greater than 4a=[3,4,5]foriina:ifi>4:a.remove(i)
Don’t make multiple passes through the list.
whileiina:a.remove(i)
Good:
Use a list comprehension or generator expression.
# comprehensions create a new list objectfiltered_values=[valueforvalueinsequenceifvalue!=x]# generators don't create another listfiltered_values=(valueforvalueinsequenceifvalue!=x)
Possible side effects of modifying the original list¶
Modifying the original list can be risky if there are other variables referencing it. But you can useslice assignment if you really want to do that.
# replace the contents of the original listsequence[::]=[valueforvalueinsequenceifvalue!=x]
Modifying the values in a list¶
Bad:
Remember that assignment never creates a new object. If two or more variables refer to the same list, changing one of them changes them all.
# Add three to all list members.a=[3,4,5]b=a# a and b refer to the same list objectforiinrange(len(a)):a[i]+=3# b[i] also changes
Good:
It’s safer to create a new list object and leave the original alone.
a=[3,4,5]b=a# assign the variable "a" to a new list without changing "b"a=[i+3foriina]
Useenumerate()
keep a count of your place in the list.
a=[3,4,5]fori,iteminenumerate(a):print(i,item)# prints# 0 3# 1 4# 2 5
Theenumerate()
function has better readability than handling acounter manually. Moreover, it is better optimized for iterators.
Read From a File¶
Use thewithopen
syntax to read from files. This will automatically closefiles for you.
Bad:
f=open('file.txt')a=f.read()print(a)f.close()
Good:
withopen('file.txt')asf:forlineinf:print(line)
Thewith
statement is better because it will ensure you always close thefile, even if an exception is raised inside thewith
block.
Line Continuations¶
When a logical line of code is longer than the accepted limit, you need tosplit it over multiple physical lines. The Python interpreter will joinconsecutive lines if the last character of the line is a backslash. This ishelpful in some cases, but should usually be avoided because of its fragility:a white space added to the end of the line, after the backslash, will break thecode and may have unexpected results.
A better solution is to use parentheses around your elements. Left with anunclosed parenthesis on an end-of-line, the Python interpreter will join thenext line until the parentheses are closed. The same behavior holds for curlyand square braces.
Bad:
my_very_big_string="""For a long time I used to go to bed early. Sometimes,\ when I had put out my candle, my eyes would close so quickly that I had not even\ time to say “I’m going to sleep.”"""fromsome.deep.module.inside.a.moduleimporta_nice_function,another_nice_function, \yet_another_nice_function
Good:
my_very_big_string=("For a long time I used to go to bed early. Sometimes, ""when I had put out my candle, my eyes would close so quickly ""that I had not even time to say “I’m going to sleep.”")fromsome.deep.module.inside.a.moduleimport(a_nice_function,another_nice_function,yet_another_nice_function)
However, more often than not, having to split a long logical line is a sign thatyou are trying to do too many things at the same time, which may hinderreadability.