FANDOM


 
Line 1: Line 1:
=== Common Python Idioms ===
+
== Common Python Idioms ==
   
==== Unpacking ====
+
=== Unpacking ===
 
Multiple assignment (also known as tuple unpacking or iterable unpacking) allows you to assign multiple variables at the same time in one line of code.
 
Multiple assignment (also known as tuple unpacking or iterable unpacking) allows you to assign multiple variables at the same time in one line of code.
 
x, y = 10, 20
 
x, y = 10, 20
Line 21: Line 21:
   
 
a, *middle, c = [1, 2, 3, 4] # a = 1, middle = [2, 3], c = 4
 
a, *middle, c = [1, 2, 3, 4] # a = 1, middle = [2, 3], c = 4
  +
===Create an Ignored Variable===
  +
If you need to assign something (i.e in unpacking) but will not need that variable, use '''__ ''':
  +
filename = 'file.txt'
  +
basename, __, ext = filename.rpartition('.')
  +
===Create a length-N list of the same thing===
  +
Use the Python list '''* '''operator:
  +
four_nones = [None] * 4
  +
===Create a length-N list of lists===
  +
Because lists are mutable, the '''* '''operator will create a list of N references to the <u>same</u> list, which is probably not what you want. Instead use a list comprehension:
  +
four_lists = [[] for __ in xrange(4)]
  +
===Create a String from a List===
  +
a common idiom for creating strings is to use '''str.join() '''on an empty string:
  +
letters = ['h', 'e', 'l', 'l', 'o']
  +
word = ''''.join(letters)''
  +
===Search for an item in a collection===
  +
Sometimes we need to search through a collection of things. Here are two options: lists & sets:
  +
s = set(['h', 'e', 'l', 'l', 'o'])
  +
l = ['h', 'e', 'l', 'l', 'o']
  +
  +
def lookup_set(s):
  +
return 'h' in s
  +
  +
def lookup_list(l):
  +
return 's' in l
  +
'''lookup_set '''is utilizing the fact that sets in Python are hashtables, therefore the lookup performance between the two is very different.
  +
  +
To determine whether an item is in a list, Python will have to go through each element until it finds a match. This can be time consuming.
  +
  +
In a set on the other hand, the hash of the item will tell Python where in the set to look for a matching item. As a result the search can be done quickly even in a large set. Searching in dictionaries works the same way. For more information see this [https://stackoverflow.com/questions/513882/python-list-vs-dict-for-look-up-table StackOverflow] page.
  +
  +
For detailed information on the amount of time various common operations take on each of these data structures, see [https://wiki.python.org/moin/TimeComplexity? this page].
  +
  +
Because of these differences in performance, it is often a good idea to '''use sets 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 duplicte items.
  +
However if you will not be frequently searching through these items, the additional time and memory required to set up the hashtable will often be greater than the time saved by improved search speed.
  +
===Zen of Python===
  +
Also known as [https://www.python.org/dev/peps/pep-0020 PEP 20], the guiding principles of Python's design.
  +
The Zen of Python, by Tim Peters
  +
  +
Beautiful 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!
  +
===PEP 8===
  +
[https://www.python.org/dev/peps/pep-0008 PEP 8] is the de facto code style guide for Python.
  +
  +
There is a command line program called '''[https://github.com/PyCQA/pycodestyle pycodestyle] '''(previously known as '''pep8''') that can check your code for conformance to PEP 8.
  +
  +
Install it by running:
  +
pip install pycodestyle
  +
Then run on a file or series of files to get a report on violations in your code:
  +
pycodestyle file.py
  +
'''[https://pypi.org/project/autopep8/ autopep8] '''can be used to automatically reformat code in the PEP 8 style. Install it by running:
  +
pip install autopep8
  +
You can use it to format a file in-place with:
  +
autopep8 --in-place file.py
  +
Running the '''--in-place '''flag will cause the program to output the modified code directly to the console for review.
  +
  +
The '''--aggressive '''flag will perform more substantial changes and can be applied multiple times for greater effect.
  +
===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 can just add it to the if statement.
  +
  +
'''BAD'''
  +
if attr == True:
  +
print 'True!'
  +
if attr == None:
  +
print 'attr is None!'
  +
'''GOOD'''
  +
# Just check the value
  +
if attr:
  +
print 'attr is truthey!'
  +
  +
# or check for the opposite
  +
if not attr:
  +
print 'attr is falsey!'
  +
  +
# or since None is considered false, explicitly check for it
  +
if attr is None:
  +
print 'attr is None!'
  +
====Access a dicionary element====
  +
Dont use the '''dict.has_key() '''method. Instead, use '''x in d '''syntax, or pass a default argument to '''dict.get()'''.
  +
  +
'''BAD'''
  +
d = {'hello': 'world'}
  +
if d.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' in d:
  +
print d['hello']
  +
====Short ways to manipulate lists====
  +
[http://docs.python.org/tutorial/datastructures.html#list-comprehensions List comprehension] provides a powerful, concise way to work with lists.
  +
  +
[http://docs.python.org/tutorial/classes.html#generator-expressions Greater expressions] follow almost the same syntax as list comprehension does but will return a greater list instead of a list.
  +
  +
Creating a new list requires more work and uses more memory. If you are jsut going to loop through the new list, prefer using an iterator instead.
  +
  +
'''BAD'''
  +
# needlessly allocates a list of all (gpa, name) entries in memory.
  +
valedictorian = max([student.gpa, student.name) for student in graduates])
  +
'''GOOD'''
  +
valedictorian = max((student.gpa, student.name) for student in graduates)
  +
Use list conprehension when you really need to create a second list, for example if you need to use result multiple times.
  +
  +
If your logic is too complicated for a short list comprehension or generator expression, consider using a generator function instead of returning a list.
  +
  +
'''GOOD'''
  +
def make_batches(items, batch_size):
  +
"""
  +
>>> list(make_batches([1, 2, 3, 4, 5], batch_size=3))
  +
[[1, 2, 3], [4, 5]]
  +
"""
  +
current_batch = []
  +
for item in items:
  +
current_batch.append(item)
  +
if len(current_batch) == batch_size:
  +
yield current_batch
  +
current_batch = []
  +
yield current_batch
  +
  +
Never use a list comprehension just for its side effects.
  +
  +
'''BAD'''
  +
  +
[print(x) for x in sequence]
  +
'''GOOD'''
  +
for x in sequence:
  +
print(x)
  +
====Filtering a list====
  +
'''BAD'''
  +
  +
Never remove items from a list while you are iterating through it.
  +
# Filter elements greater than 4.
  +
a = [3, 4, 5]
  +
for i in a:
  +
if i > 4:
  +
a.remove(i)
  +
Don't make multiple passes through the list.
  +
while i in a:
  +
a.remove(i)
  +
'''GOOD'''
  +
Use a list comprehension or generator expression.
  +
# Comprehensions create a new list object.
  +
filtered_values = [value for value in sequence if value != x]
  +
  +
# Generators don't create another list.
  +
filtered_values = (value for value in sequence if value != 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 use '''slice''' '''assignment '''if you really want to do that.
  +
# replace the contents of the original list.
  +
sequence[::] = [value for value in sequence if value != 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 all of them.
  +
# Add three to all the list members.
  +
a = [3, 4, 5]
  +
b = a # a and b refer to the same list object.
  +
  +
for i in range(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 + 3 for i in a]
  +
Use '''enumerate() '''to keep count of your place in the list.
  +
a = [3, 4, 5]
  +
for i, item in enumerate(a):
  +
print i, item
  +
# prints:
  +
# 0 3
  +
# 1 4
  +
# 2 5
  +
The '''enumerate() '''funciton has better readability than handling a counter manually. Moreover, it is better optimized for iterators.
  +
====Read from a file====
  +
Use the '''with open''' syntax to read from files. This will automatically close files for you.
  +
  +
'''BAD'''
  +
f = open('file.txt')
  +
a = f.read()
  +
print a
  +
f.close()
  +
'''GOOD'''
  +
with open('file.txt' as f):
  +
for line in f:
  +
print line
  +
====Line continuations====
  +
When a logical line of code is longer that the accepted limit, you need to split it over multiple physical lines. The Python interpreter will join consecutive lines if the last character of the line is a backslash. This is helpful 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 the code and may have unexpected results.
  +
  +
A better solution is to use parentheses around your elements. Left with an unclosed parentheses on an end-of-line the Python interpreter will join the next line until the parentheses are closed. The same behavior holds for curly and 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.”"""
  +
  +
from some.deep.module.inside.a.module import a_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.”"
  +
)
  +
  +
from some.deep.module.inside.a.module import (
  +
a_nice_function, another_nice_function, yet_another_nice_function)
  +
However, more often and not, having to split a long logical line is a sign that you are trying to do too many things as the same time, which may hinder readability.
  +
===References===
  +
*https://docs.python-guide.org/writing/style/
  +
*https://github.com/hblanks/zen-of-python-by-example
  +
*https://pep8.org/
  +
*https://docs.python.org/3/library/stdtypes.html#truth-value-testing

Latest revision as of 01:41, December 11, 2019

Common Python Idioms Edit

Unpacking Edit

Multiple assignment (also known as tuple unpacking or iterable unpacking) allows you to assign multiple variables at the same time in one line of code.

x, y = 10, 20

What's happening at a lower level in the above code is:

# Creating a tuple of 10, 20 and then looping over the tuple and taking each of the two items we get from looping and assigning them to x & y.
(x, y) = (10, 20)

If you know the length of a list or tuple, you can assign names to its elements with unpacking.

For example, we can use enumerate() which will provide a tuple of two elements for each item in a list:

for index, item in enumerate(some_list):
    # do something with index and item

You can use this to swap variables too:

a, b = b, a

Nested unpacking:

a, (b, c) = 1, (2, 3)

New introduced way of unpacking:

a, *the_rest = [1, 2, 3]  # a = 1, the_rest = [2, 3]


a, *middle, c = [1, 2, 3, 4]  # a = 1, middle = [2, 3], c = 4

Create an Ignored VariableEdit

If you need to assign something (i.e in unpacking) but will not need that variable, use __ :

filename = 'file.txt'
basename, __, ext = filename.rpartition('.')

Create a length-N list of the same thingEdit

Use the Python list * operator:

four_nones = [None] * 4

Create a length-N list of listsEdit

Because lists are mutable, the * operator will create a list of N references to the same list, which is probably not what you want. Instead use a list comprehension:

four_lists = [[] for __ in xrange(4)]

Create a String from a ListEdit

a common idiom for creating strings is to use str.join() on an empty string:

letters = ['h', 'e', 'l', 'l', 'o']
word = ''.join(letters)

Search for an item in a collectionEdit

Sometimes we need to search through a collection of things. Here are two options: lists & sets:

s = set(['h', 'e', 'l', 'l', 'o'])
l = ['h', 'e', 'l', 'l', 'o']

def lookup_set(s):
    return 'h' in s

def lookup_list(l):
    return 's' in l

lookup_set is utilizing the fact that sets in Python are hashtables, therefore the lookup performance between the two is very different.

To determine whether an item is in a list, Python will have to go through each element until it finds a match. This can be time consuming.

In a set on the other hand, the hash of the item will tell Python where in the set to look for a matching item. As a result the search can be done quickly even in a large set. Searching in dictionaries works the same way. For more information see this StackOverflow page.

For detailed information on the amount of time various common operations take on each of these data structures, see this page.

Because of these differences in performance, it is often a good idea to use sets 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 duplicte items.

However if you will not be frequently searching through these items, the additional time and memory required to set up the hashtable will often be greater than the time saved by improved search speed.

Zen of PythonEdit

Also known as PEP 20, the guiding principles of Python's design.

The Zen of Python, by Tim Peters

Beautiful 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!

PEP 8Edit

PEP 8 is the de facto code style guide for Python.

There is a command line program called pycodestyle (previously known as pep8) that can check your code for conformance to PEP 8.

Install it by running:

pip install pycodestyle

Then run on a file or series of files to get a report on violations in your code:

pycodestyle file.py

autopep8 can be used to automatically reformat code in the PEP 8 style. Install it by running:

pip install autopep8

You can use it to format a file in-place with:

autopep8 --in-place file.py

Running the --in-place flag will cause the program to output the modified code directly to the console for review.

The --aggressive flag will perform more substantial changes and can be applied multiple times for greater effect.

ConventionsEdit

Here are some conventions you should follow to make your code easier to read.

Check if a variable equals a constantEdit

You don't need to explicitly compare a value to True or None or 0. You can just add it to the if statement.

BAD

if attr == True:
    print 'True!'
if attr == None:
    print 'attr is None!'

GOOD

# Just check the value
if attr:
    print 'attr is truthey!'

# or check for the opposite
if not attr:
    print 'attr is falsey!'

# or since None is considered false, explicitly check for it
if attr is None:
    print 'attr is None!'

Access a dicionary elementEdit

Dont use the dict.has_key() method. Instead, use x in d syntax, or pass a default argument to dict.get().

BAD

d = {'hello': 'world'}
if d.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' in d:
    print d['hello']

Short ways to manipulate listsEdit

List comprehension provides a powerful, concise way to work with lists.

Greater expressions follow almost the same syntax as list comprehension does but will return a greater list instead of a list.

Creating a new list requires more work and uses more memory. If you are jsut going to loop through the new list, prefer using an iterator instead.

BAD

# needlessly allocates a list of all (gpa, name) entries in memory.
valedictorian = max([student.gpa, student.name) for student in graduates])

GOOD

valedictorian = max((student.gpa, student.name) for student in graduates)

Use list conprehension when you really need to create a second list, for example if you need to use result multiple times.

If your logic is too complicated for a short list comprehension or generator expression, consider using a generator function instead of returning a list.

GOOD

def make_batches(items, batch_size):
    """
    >>> list(make_batches([1, 2, 3, 4, 5], batch_size=3))
    [[1, 2, 3], [4, 5]]
    """
    current_batch = []
    for item in items:
        current_batch.append(item)
        if len(current_batch) == batch_size:
            yield current_batch
            current_batch = []
    yield current_batch

Never use a list comprehension just for its side effects.

BAD

[print(x) for x in sequence]

GOOD

for x in sequence:
    print(x)

Filtering a listEdit

BAD

Never remove items from a list while you are iterating through it.

# Filter elements greater than 4.
a = [3, 4, 5]
for i in a:
    if i > 4:
        a.remove(i)

Don't make multiple passes through the list.

while i in a:
    a.remove(i)

GOOD Use a list comprehension or generator expression.

# Comprehensions create a new list object.
filtered_values = [value for value in sequence if value != x]

# Generators don't create another list.
filtered_values = (value for value in sequence if value != x)

Possible side effects of modifying the original listEdit

Modifying the original list can be risky if there are other variables referencing it. But you can use slice assignment if you really want to do that.

# replace the contents of the original list.
sequence[::] = [value for value in sequence if value != x]

Modifying the values in a listEdit

BAD

Remember that assignment never creates a new object. If two or more variables refer to the same list, changing one of them changes all of them.

# Add three to all the list members.
a = [3, 4, 5]
b = a              # a and b refer to the same list object.

for i in range(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 + 3 for i in a]

Use enumerate() to keep count of your place in the list.

a = [3, 4, 5]
for i, item in enumerate(a):
    print i, item
# prints:
# 0 3
# 1 4
# 2 5

The enumerate() funciton has better readability than handling a counter manually. Moreover, it is better optimized for iterators.

Read from a fileEdit

Use the with open syntax to read from files. This will automatically close files for you.

BAD

f = open('file.txt')
a = f.read()
print a
f.close()

GOOD

with open('file.txt' as f):
    for line in f:
        print line

Line continuationsEdit

When a logical line of code is longer that the accepted limit, you need to split it over multiple physical lines. The Python interpreter will join consecutive lines if the last character of the line is a backslash. This is helpful 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 the code and may have unexpected results.

A better solution is to use parentheses around your elements. Left with an unclosed parentheses on an end-of-line the Python interpreter will join the next line until the parentheses are closed. The same behavior holds for curly and 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.”"""

from some.deep.module.inside.a.module import a_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.”"
)

from some.deep.module.inside.a.module import (
   a_nice_function, another_nice_function, yet_another_nice_function)

However, more often and not, having to split a long logical line is a sign that you are trying to do too many things as the same time, which may hinder readability.

ReferencesEdit

Community content is available under CC-BY-SA unless otherwise noted.