Python Style Guide

## Common Python Idioms

### 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.

```
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 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 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 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 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 Python

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 8

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.

### 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

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 list

**BAD**

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

```
# Filter elements greater than 4.
a = [3, 4, 5]
        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
# 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**

```
        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