Idiomatic Python - miscellaneous part 1¶

Comprehensions¶

In [1]:
original_data = (1, 2, 3, 4)

Don't do this.

In [2]:
# list
square_roots_list = []
for val in original_data:
    square_root = val ** (1 / 2)
    square_roots_list.append(square_root)
print(square_roots_list)

# set
square_roots_set = set()
for val in original_data:
    square_root = val ** (1 / 2)
    square_roots_set.add(square_root)
print(square_roots_set)

# dict
square_roots_dict = {}
for val in original_data:
    square_root = val ** (1 / 2)
    square_roots_dict[val] = square_root
print(square_roots_dict)

# dict with a condition
integer_square_roots_dict = {}
for val in original_data:
    square_root = val ** (1 / 2)
    if square_root.is_integer():
        integer_square_roots_dict[val] = square_root
print(integer_square_roots_dict)
[1.0, 1.4142135623730951, 1.7320508075688772, 2.0]
{1.0, 2.0, 1.7320508075688772, 1.4142135623730951}
{1: 1.0, 2: 1.4142135623730951, 3: 1.7320508075688772, 4: 2.0}
{1: 1.0, 4: 2.0}

Note: in case you're using 2.X version of Python for some reason, the result of 1/2 is 0 instead of 0.5.

Use comprehensions!¶

In [3]:
square_roots_list = [val ** (1 / 2) for val in original_data]
print(square_roots_list)

square_roots_set = {val ** (1 / 2) for val in original_data}
print(square_roots_set)

square_roots_dict = {val: val ** (1 / 2) for val in original_data}
print(square_roots_dict)

integer_square_roots_dict = {
    val: val ** (1 / 2) for val in original_data if (val ** (1 / 2)).is_integer()
}
print(integer_square_roots_dict)
[1.0, 1.4142135623730951, 1.7320508075688772, 2.0]
{1.0, 2.0, 1.7320508075688772, 1.4142135623730951}
{1: 1.0, 2: 1.4142135623730951, 3: 1.7320508075688772, 4: 2.0}
{1: 1.0, 4: 2.0}

Using in for checking presence of an element in a collection¶

In [4]:
name = "John Doe"

Don't do it like this.

In [5]:
if name == "John" or name == "Doe" or name == "John Doe":
    print("This seems to be our guy")
This seems to be our guy

Do it like this!¶

In [6]:
if name in ("John", "Doe", "John Doe"):
    print("This seems to be our guy")
This seems to be our guy

Chained comparisons¶

In [7]:
a, b, c, d = 1, 2, 3, 4

Don't do it like this.

In [8]:
if b > a and c > b and d > c:
    print("from lowest to highest: a, b, c, d")
from lowest to highest: a, b, c, d

Do it like this!¶

In [9]:
if a < b < c < d:
    print("from lowest to highest: a, b, c, d")
from lowest to highest: a, b, c, d

Falsy/truthy values¶

In [10]:
# These are falsy
my_list = []
my_dict = {}
my_set = set()
my_tuple = tuple()
zero = 0
false = False
none = None
my_str = ""

# Basically the rest are truthy
# for example:
my_second_list = ["foo"]

Don't do it like this.

In [11]:
if len(my_list) == 0:
    print("Empty list is so empty")

if not len(my_dict):
    print("Empty dict is also very empty")

if not len(my_set) and not len(my_tuple):
    print("Same goes for sets and tuples")

if not bool(zero) and not bool(false) and not bool(none) and len(my_str) == 0:
    print("These are also falsy")

if len(my_second_list) > 0:
    print("This should be true")
Empty list is so empty
Empty dict is also very empty
Same goes for sets and tuples
These are also falsy
This should be true

This is much better!¶

In [12]:
if not my_list:
    print("Empty list is so empty")

if not my_dict:
    print("Empty dict is also very empty")

if not my_set and not my_tuple:
    print("Same goes for sets and tuples")

if not zero and not false and not none and not my_str:
    print("These are also falsy")

if my_second_list:
    print("This should be true")
Empty list is so empty
Empty dict is also very empty
Same goes for sets and tuples
These are also falsy
This should be true

any & all¶

In [13]:
example_collection = ["a", True, "Python is cool", 123, 0]

Don't do it like this.

In [14]:
any_value_truthy = True
for val in example_collection:
    if val:
        any_value_truthy = True
        break

all_values_truthy = True
for val in example_collection:
    if not val:
        all_values_truthy = False
        break

print(f"any truthy: {any_value_truthy}, all truthy: {all_values_truthy}")
any truthy: True, all truthy: False

Do it like this!¶

In [15]:
any_value_truthy = any(example_collection)
all_values_truthy = all(example_collection)
print(f"any truthy: {any_value_truthy}, all truthy: {all_values_truthy}")
any truthy: True, all truthy: False

Pythonic substitute for ternary operator¶

Many other programming languages have a ternary operator: ?. A common use case for the ternary operator is to assign a certain value to a variable based on some condition. In other words, it could be used like this:

variable = some_condition ? some_value : some_other_value

Instead of doing this.

In [16]:
some_condition = True  # just a dummy condition

if some_condition:
    variable = "John"
else:
    variable = "Doe"
print(variable)
John

You can do it like this!¶

In [17]:
variable = "John" if some_condition else "Doe"
print(variable)
John

Function keywords arguments¶

For better readability and maintainability.

In [18]:
def show_person_details(name, is_gangster, is_hacker, age):
    print(f"name: {name}, gangster: {is_gangster}, hacker: {is_hacker}, age: {age}")

This is not good. It's hard to tell what True, False and 83 refer here if you are not familiar with the signature of the show_person_details function.

In [19]:
show_person_details("John Doe", True, False, 83)
name: John Doe, gangster: True, hacker: False, age: 83

This is much better!¶

In [20]:
show_person_details("John Doe", is_gangster=True, is_hacker=False, age=83)
name: John Doe, gangster: True, hacker: False, age: 83

Extra: keyword only arguments after *¶

This might be useful for example if the signature of the function is likely to change in the future. For example, if there's even a slight chance that one of the arguments may be dropped during the future development, consider using *.

In [21]:
def func_with_loads_of_args(arg1, *, arg2=None, arg3=None, arg4=None, arg5="boom"):
    pass


# This won't work because only keyword arguments allowed after *
# func_with_loads_of_args('John Doe', 1, 2)

# This is ok
func_with_loads_of_args("John Doe", arg4="foo", arg5="bar", arg2="foo bar")

Multiple assigment¶

Let's say we want to swap the values of two variables.

Don't do it like this.

In [22]:
# original values
a = 1
b = 2

# swap
tmp = a
a = b
b = tmp
print(a, b)
2 1

Do it like this!¶

In [23]:
# original values
a = 1
b = 2

# swap
a, b = b, a
print(a, b)
2 1

(Un)packing¶

In [24]:
my_list = [1, 2, 3, 4, 5, 6]

Don't do something like this.

In [25]:
first = my_list[0]
last = my_list[-1]
middle = my_list[1:-1]
print(first, middle, last)

packed = [first] + middle + [last]
assert packed == my_list
1 [2, 3, 4, 5] 6

This is the Pythonic way!¶

In [26]:
# unpacking
first, *middle, last = my_list
print(first, middle, last)

# packing
packed = [first, *middle, last]
assert packed == my_list
1 [2, 3, 4, 5] 6