Skip to content

Instantly share code, notes, and snippets.

@JohnStuartRutledge
Last active February 23, 2023 09:56
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save JohnStuartRutledge/5688821 to your computer and use it in GitHub Desktop.
Save JohnStuartRutledge/5688821 to your computer and use it in GitHub Desktop.
A gist for tracking examples of idiomatic Python. The goal is to accrue a list of concrete examples to help develop an intuition of what constitutes "Pythonic" code.

This page is designed to give you a list of concrete examples demonstrating idiomatic (Pythonic) code.

Many of the examples were taken from the following resources:

Summary

Python philosophically distinguishes itself from other programming languages by it's particularly strong emphasis on readability –code that conforms itself to known best practices/standards.

Within the Python community, well written code is said to be "Pythonic" if it adheres to the Python philosophy. People who have mastered coding in a Pythonic way are known as "Pythonistas."

The most canonical example of the tenets that distinguish un-Pythonic from Pythonic code can be found in Tim Peters famous poem, "The Zen of Python." To read it you need only open a Python prompt on your command line, and type the words, "import this."

The Zen of Python

>>> import this
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!

The two lines most relevant to this page are

  • Readability counts
  • There should be one-- and preferably only one -- obvious way to do it

Idioms

Name Formatting

Indicate seperate words in variable names using underscores

my_variable_name = True

Indicate seperate words in function names using underscores

def my_function_name():
    pass

Indicate seperate words in class names using CamelCase

class MyClassName(object):
    pass

Prefix a single underscore to class members/functions meant for internal use only

class MyClassName(object):
    def _internal_function(self):
        pass

Use all caps to indicate constants

class ConnectDB(object):
    DB = "mysql",
    USER = "Steve",
    PASS = "fzHx$"
    def __init__(self):
       pass

List of GOOD and BAD Examples

Use 4 spaces per indentation level

BAD

if x is True:
  print(x)

GOOD

if x is True:
    print(x)

Limit the length of your lines to 79 characters in width

BAD

def my_function(a, b, c):
    my_str = "a, b, and c contains the following values {0}, {1}, {2}".format(a, b, c)

GOOD

def my_function(a, b, c):
    my_str = "a, b, and c contains the following values {0}, {1}, {2}".format(
        a, b, c)

Create readable multi-line text with the use of parentheses

BAD

big_string = """This is the first line in the text \
this is the second line in the text \
this is the third line in the text"""

GOOD

big_string = (
    "This is the first line in the text "
    "this is the second line in the text "
    "this is the third line in the text"
    )

In general, try to only import one module per line

BAD

import sys, os, requests

GOOD

import os
import sys
import requests

Avoid using a temporary variable when swapping two variables

BAD

c = a
a = b
b = c

GOOD

a, b = b, a

Use tuples to unpack data

BAD

li = [1, 2, 3]
a = li[0]
b = li[1]
c = li[2]

GOOD

li = [1, 2, 3]
(a, b, c) = li

Use ''.join() on an empty string to concatonate a collection of strings

BAD

li = ['a', 'b', 'c']
result = ''
for x in li:
    result += x

GOOD

li = ['a', 'b', 'c']
result = ''.join(li)

Use dict.get() to return a default value from a dictionary

BAD

x = None
if 'item' in my_dict:
    x = my_dict['item']
else:
    x = 'fallback value'

GOOD

x = my_dict.get('item', 'fallback value')

Open files using using the with statement

BAD

f = open(path_to_file)
for line in f.readlines():
    # do something...
f.close()

GOOD

with open(path_to_file) as f:
    for line in f:
        # do something...
# no need to call close()

Avoid repeating a variable within a compound if statement

BAD

if name=='Tom' or name=='Sam' or name=='Ron':
    pass

GOOD

if name in ('Tom', 'Sam', 'Ron'):
    pass

Don't place conditional code on the same line as the colon

BAD

if item: print(item)

GOOD

if item:
    print(item)

Avoid putting multiple statements on a single line

BAD

if condition: my_function(); my_other_function();

GOOD

if condition:
    my_function()
    my_other_function()

Use list comprehensions to create lists that are subsets of existing data

BAD

l1 = range(1, 100)
l2 = []
for x in l1:
    if is_prime(x):
        l2.append(x)

GOOD

l1 = range(1, 100)
l2 = [x for x in l1 if is_prime(x)]

Use the 'in' keyword to iterate over an Iterable

BAD

li = ['a', 'b', 'c']
index = 0
while index < len(li):
    print(li[index])
    index += 1

GOOD

li = ['a', 'b', 'c']
for element in li:
    print(element)

Use the 'enumerate' built-in function to keep count on an Iterable

BAD

li = ['a', 'b', 'c']
index = 0
for item in li:
    print(index, item)
    index += 1

GOOD

for index, item in enumerate(li):
    print(index, item)

Create a length-N list composed of the same specific item

BAD

four_nones = []
for x in range(1,5):
    four_nones.append(None)

GOOD

four_nones = [None] * 4

Create a length-N list composed of empty lists

BAD

li = []
for _ in range(1, 5):
    li.append([])

GOOD

li = [[] for _ in range(4)]

Use isinstance() to do object type comparisons

BAD

x = 5
if type(x) is type(1):
    print('x is an int')

GOOD

x = 5
if isinstance(x, int):
    print('x is an int')

For strings, lists, and tuples, use the fact that empty sequences are false

BAD

x = []
if not len(x):
    print('x is false')

GOOD

x = []
if not x:
    print('x is false')

Avoid using a mutable values as a default function parameter

BAD

def fn(x=[]):
    x.append(1)
    print(x)

GOOD

def fn(x=None):
    x = [] if x is None else x
    x.append(1)
    print(x)

Check that all items in a large set of items are true

BAD

if a and b and c and d and e and f and g and h:
    print('all true')

GOOD

if all([a, b, c, d, e, f, g, h]):
    print('all true')

Check if at least one item in a set of three or more items are true

BAD

if a or b or c:
    print('at least one is true')

GOOD

if any([a, b, c]):
    print('at least one is true')

Be careful how you use the is operator when checking for empty/unset variables.
Inherently falsy values like None, [], (), "", '', and 0 do not all mean the same thing.

BAD

import random
x = random.choice([ None, [] ])

if x is not None:
    print(x) 
    
# if x=[] then print(x) will run

GOOD

import random
x = random.choice([ None, [] ])

if x:
    print(x)
# neither None or [] will pass the if statment

Be aware of scope. Using the same variable name for a global and local variables can lead to errors

BAD

x = 1
def fn():
    print(x) 
    x = 2

fn()     # UnboundLocalError: local variable 'x' referenced before assignment
print(x) # prints 1

GOOD

# things work as expected as long as we don't locally redfine x in the function scope
x = 1
def fn():
    print(x)
    y = 2

fn()     # prints 1
print(x) # prints 1


# to modify the global variable x, you could do
x = 1
def fn():
    global x
    print(x)
    x = 2

fn()     # prints 1
print(x) # prints 2

Take advantage of extended slice notation to reverse a string

BAD

backward = ''
for c in reversed('hello'):
    backward += c

print(backward) # 'olleh'

GOOD

# the faster albiet less readable approach is
backward = 'hello'[::-1]

# the slower but more readable approach is:
backward = ''.join(reversed('hello'))

print(backward) # 'olleh'

Counting frequency

BAD

ls = ['a', 'a', 'b', 'c', 'a']
foo = {}
for x in ls:
    if x in foo:
        foo[x] += 1
    else:
        foo[x] = 1

print(foo) # {'a': 3, 'b': 1, 'c': 1}

GOOD

from collections import Counter

ls = ['a', 'a', 'b', 'c', 'a']
foo = Counter(ls)
print(foo) # Counter({'a': 3, 'b': 1, 'c': 1})

Use for..else to run some code if the break statment is never reached. BAD

# bad example here that uses a tracking variable to determine if break has run or not

GOOD

 
for f in file_list:
    if is_img(f):
        break
else: # no break encountered
    batch_process_files(file_list)

Good Practices

Add __repr__ to all your classes for more beautiful debugging

class Example:
    def __init__(self, x):
        self.x = x
    
    def __repr__(self):
        return 'Example(x={})'.format(self.x)

Misc Observations For Programming in Python

Use virtualenv - don't install python packages at the system level

Take advantage of ipython/Juptyers profiling functions like %time and %prun to profile code.

Avoid *args and **kwargs unless you need them - they make function signatures hard to read and code-completion less helpful

Incorporate a linting tool like pyflakes or pylint into your IDE.

Learn to approach unit tests as a brain-storming exercise on how best to write your application.

In general prefer smaller individual functions dedicated to doing a single thing to larger functions that do many things.

Give every function a docstring.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment