3 minute read

Pythonic Thinking

1. Know the version of your Python

import sys
print(sys.version_info)
print(sys.version)

2. Follow the PEP 8 Style Guide

  • Within a file, function and classes should be seperated by 2 blank lines
  • In a class, methods should be seperated by 1 blank line
  • For type annotations, ensure that there is no separation between the variable name and the colon, and use a space before the type information
import numpy as np


def element_wise_add(data1: np.ndarray, data: np.ndarray) -> np.ndarray:
    return data1 + data
  • Protected instance attributes should be in _leading_underscore format
  • Private instance attributes should be in __double_leading_underscore format
  • Module-level constraints should be in ALL_CAPS format
  • Instance methods in classes should use self, which refers to the object, as the name of the first parameter
  • Don’t check for empty containers or sequences by comparing the length to zero (if len(somelist) == 0). Use if not somelist and assume that empty values will implicitly evaluate to False
  • Always put import statements at the top of a file
  • Always use absolute names when importing them, not names relative to the current module’s own path. (from bar import foo, not just from . import foo)

3. Know the difference between bytes and str

  • Instances of bytes contain raw, unsigned 8-bit values (often in ASCII encoding)
  • Instances of str contain Unicode code points that represent textual characters from human language
  • To convert Unicode data to binary data, you must call encode method, and to convert binary to Unicode, you must call the decode method
# Takes a str or bytes and always returns a str
def to_str(bytes_or_str):
    if isinstance(bytes_or_str, bytes):
        value = bytes_or_str.decode('utf-8')
    else:
        value = bytes_or_str
    return value

# Takes a str or bytes and always returns a byte
def to_byte(bytes_or_str):
    if isinstance(bytes_or_str,str):
        value = bytes_or_str.encode('utf-8')
    else:
        value = bytes_or_str
    return value
  • Write and read a file with (‘wb’,’rb’) if the file is written in binary mode (bytes)

4. Prefer Interpolated F-Strings over C-style format Strings and str.format

pantry = [("Avocados", 1), ("Bananas", 2), ("Cherries", 15)]
for i, (item, count) in enumerate(pantry):
    print(f'#{i+1} : {item.title():<10s} = {round(count)}')

places, number = 3, 1.23456
print(f'My number is approximately {number:.{places}f}')
  • F-strings are succinct yet powerful because they allow for arbitrary Python expressions to be directly embedded within format specifiers

5. Write Helper Functions Instead of Complex Expressions

  • Empty string, the empty list, and zero all evaluate to False implicitly
example = {'red' : ['5'], 'blue': ['0'], 'green': ['']}

red = int(example.get('red', [''])[0] or 0)
# empty string is evaluated as false
green = int(example.get('green', [''])[0] or 0)
# non-existent key leads to the list with empty string
white = int(example.get('white', [''])[0] or 0) 

# written with ternary expression 
red = example.get('red', [''])
red_str = int(red[0]) if red[0] else 0

# written with helper function
def get_first_int(values, key, default = 0):
    found = values.get(key, [''])
    if found[0]:
      return int(found[0])
    return default
  • Always prefer a more readable code over a concise one

6. Prefer Multiple Assignment Unpacking over Indexing

  • When indexing a tuple, objects are immutable whereas unpacking makes them mutable objects
  • Swapping indexes within a line is possible!
def bubble_sort(a):
  for _ in range(len(a)):
    for i in range(1,len(a)):
      if a[i] < a[i-1]:
        a[i-1], a[i] = a[i], a[i-1]
  • The right side of the assignment is always evaluated first, and its values are put into a new temporary unnamed tuple.
  • Then, the unpacking pattern from the left side of the assignment a[i-1], a[i] is used to receive that unnamed tuple value and assign.
  • Finally, the unnamed tuple silently goes away…

7. Prefer enumerate over Range

  • Enumerate wraps any iterator with a lazy generator and yields pairs of the loop index and next value from the given iterator.
  • The second parameter of enumerate controls where to begin the count (default=0)
flavors = ['vanilla', 'chocolate', 'raspberry', 'oreo']
for i, flavor in enumerate(flavors, 1): 
    print(f'{i+1} : {flavor}')

8. Use zip to Precess Iterators in Parallel

term
definition

Tags:

Categories:

Updated: