Python tips

6 minute read

Virtual environments

If you are working on multiple projects, you may find yourself with conflicts between the installed packages and the ones you need to use for another project. Sometimes you may even have to use different versions of Python.

Python virtual environments allow you to isolate the packages and binaries that each project needs, avoiding these kind of problems.

  • virtualenv: probably the oldest one on this list and a bit outdated. I would recommend you to also install virualenvwrapper to go with it.
  • venv: pretty much like virtualenv but included in the standard library.
  • pipenv: recommended by The Python Packaging Guide, combines Pipfile, pip and virtualenv into a single tool.
  • conda: heavy package and environment manager, easy to use and recommended by PyTorch.

PyCharm

PyCharm is a very powerful IDE for Python, it comes with:

  • A default PEP-8 linter.
  • Code completion and error checking.
  • Easy documentation browser.
  • Virtual Environments manager.
  • Integrated terminal and version control utilities.
Jetbrains

Linters / Code formatters

  • A linter, is a tool that analyzes source code to flag programming errors, bugs, stylistic errors, and suspicious constructs.
  • A code formatter changes your code according to set standards in order to make it easily navigable and readable.

A linter is a must have in any programming project, it makes your code more consistent, easier to read and to maintain.

I highly recommend using an automatic code formatter, it saves you time and energy to get things done instead of worrying about code style or fixing the linting errors.

  • autopep8: oldest tool of this list, makes your code conform to the PEP-8 style guide.
  • yapf: maintained by Google and very configurable.
  • black: opinionated formatter created by a Python Core Developer, keeps discussions to a minimum by deciding everything for you.

Type Hints

Python is a dynamically typed language, but that doesn’t mean you can’t have type checking and autocompletion with a good IDE and some type hinting.

def string_to_number(string: str) -> int:
    # do something
    return number

In this example PyCharm will show a warning if we pass something to our function that is not a string and it will also show us all the methods available from the string class when dealing with the string parameter.

Coding tips

Here are some useful tips for writing cleaner code:

f-strings

Introduced on Python 3.6, it is a great way to format strings. Just put f before the quotes of your string and put your variables between brackets.

x, y, z = 1.0, 2.0, 3.0
# f-strings
print(f'The coordinates are: {x}, {y}, {z}')

# Old way
print('The coordinates are: %s, %s, %s' % (x, y, z))

# .format()
print('The coordinates are: {}, {}, {}'.format(x, y, z))
print('The coordinates are: {0}, {1}, {2}'.format(x, y, z))
print('The coordinates are: {x}, {y}, {z}'.format(x=x, y=y, z=z))

Built-in Functions

Built-in functions are there for a reason, you may already know the most common ones:

  • dict, int, float, len, list, print, range, set, str, sum, tuple

So let’s learn about some others:

  • enumerate(): Allows looping over something and having an automatic counter.
my_list = ['a', 'b', 'c', 1, 2]
# Write
for idx, value in enumerate(my_list):
    print(f'Index: {idx}, Value: {value}')

# Instead of
for idx in range(len(my_list)):
    print(f'Index: {idx}, Value: {my_list[idx]}')
  • reversed(): Iterate in reverse order.
for i in reversed([1,2,3,4]):
    print(i)
# 4, 3, 2, 1
  • min() and max(): Get the minimum and maximum items in an iterable.
min([1, 2, 3]) # 1
min([1, 2, 3], key=lambda x: x%3) # 3 (3 mod 3 == 0)

max([1, 2, 3]) # 3
max([1, 2, 3], key=lambda x: x%3) # 2 (2 mod 3 == 2)
  • sorted(): Returns a new sorted list.
sorted([4, 2, 1, 3]) # [1, 2, 3 ,4]
sorted([4, 2, 1, 3], reverse=True) # [4, 3, 2, 1]

sorted(["Hello", "my", "name", "is", "Xiang"])  # ['Hello', 'Xiang', 'is', 'my', 'name']
sorted(["Hello", "my", "name", "is", "Xiang"], key=len)  # ['is', 'my', 'name', 'Hello', 'Xiang']
  • any() and all(): Check if any or all items match a condition.
numbers = [1, 2, 3, 4]
any(n%2 == 0 for n in numbers)  # True
all(n%2 == 0 for n in numbers)  # False

even = [2, 4, 6, 8]
all(n%2 == 0 for n in even)  # True
any(n%2 == 1 for n in even)  # False

collections

A standard library module that provides some interesting datatypes.

  • namedtuple(): quick way to create tuples with fields.
from collections import namedtuple

Point = namedtuple('Point', ['x', 'y'])
p = Point(11, y=22)
p # Point(x=11, y=22)

p[0]  # 11
p.x   # 11

x, y = p
x, y  # (11, 22)
  • Counter: dict subclass for counting hashable objects.
from collections import Counter

c = Counter(["a", "b", "b", "c", "d", "d", "d"])
# Counter({'d': 3, 'b': 2, 'a': 1, 'c': 1})
  • defaultdict: dict subclass that returns a default value if a key is missing.
from collections import defaultdict

d = defaultdict(list)
for w in ["Paris", "Barcelona", "London", "Lisbon", "Bogotá"]:
    first_letter = w[0]
    d[first_letter].append(w)  # does not raise any errors

# defaultdict(<class 'list'>, {
#   'P': ['Paris'],
#   'B': ['Barcelona', 'Bogotá'],
#   'L': ['London', 'Lisbon']
# })

Sets

A set is an unordered collection with no duplicate elements.

Consider using sets when:

# Need unique values
set([1,2,3,1,1,5,2]) # {1, 2, 3, 5}

# Are doing something like this where order doesn't matter
my_list = [1, 3, 5, 7, 9]
for i in range(10):
    if i not in my_list:
        my_list.append(i)

# Using sets
my_set = {1, 3, 5, 7, 9}
for i in range(10):
    my_set.add(i)  # won't duplicate

# Need fast lookup on large amounts of data
3 in my_set  # O(1)
3 in my_list # O(n)

argparse

Instead of using input() to ask for parameters for you script, consider using command-line options.

import argparse

parser = argparse.ArgumentParser()
parser.add_argument("-i", "--input", type=str, help="input file")
parser.add_argument("-o", "--output", type=str, default="output.txt")
args = parser.parse_args()

print(f'I will process {args.input} and write into {args.output}')
# $ python script.py -i input.txt -o output.o
#> I will process input.txt and write into output.o

# $ python script.py -i test
#> I will process test and write into output.txt

List Comprehensions

If you use them correctly, they can be more readable than loops, filter or map.

even_numbers = [i for i in range(10) if i % 2 == 0]
# [0, 2, 4, 6, 8]

odd_numbers = [i for i in range(10) if i not in even_numbers]
# [1, 3, 5, 7, 9]

squares = [i**2 for i in range(10)]
# [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

Careful though:

# Not very readable
flat = [x for sublist1 in my_lists
        for sublist2 in sublist1
        for x in sublist2]

# Indentation helps
flat = []
for sublist1 in my_lists:
    for sublist2 in sublist1:
        flat.extend(sublist2)

Other Comprehensions

Don’t forget there are all also set and dictionary comprehensions.

# Set comprehensions
names = ['BoB', 'bob', 'BOB', 'Alice', 'Jordan', 'JORDan']
names_set = {name.lower() for name in names}
# {'jordan', 'alice', 'bob'}

# Dictionary comprehensions
characters = {name: len(name) for name in names}
# {'BoB': 3, 'bob': 3, 'BOB': 3, 'Alice': 5, 'Jordan': 6, 'JORDan': 6}

Keyword Arguments

Using keyword arguments may make your code a lot more readable.

def loan_money(amount, interest, lender, borrower):
    pass

loan_money(300, 0.2, 'Bob', 'Alice')
# What does the 0.2 mean? Who is the lender?

loan_money(300, interest=0.2, lender='Bob', borrower='Alice')

You can even force some arguments to be keyword only!

# The * symbol indicates the beginning of keyword-only arguments
def loan_money(amount, interest=0.1, *, lender, borrower):
    pass

loan_money(300, 0.2, 'Bob', 'Alice')
# TypeError: loan_money() takes from 1 to 2 positional arguments but 4 were given

loan_money(300, lender='Bob', borrower='Alice')
# OK, interest will be 0.1 (default value)

Other interesting reads

Leave a comment