# Idiomatic Python - miscellaneous part 1¶

## Comprehensions¶

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


Don't do this.

In :
# 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 :
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 :
name = 'John Doe'


Don't do it like this.

In :
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 :
if name in ('John', 'Doe', 'John Doe'):
print('This seems to be our guy')

This seems to be our guy


## Chained comparisons¶

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


Don't do it like this.

In :
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 :
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 :
# 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 :
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 :
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 :
example_collection = ['a', True, 'Python is cool', 123, 0]


Don't do it like this.

In :
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('any truthy: {}, all truthy: {}'.format(any_value_truthy, all_values_truthy))

any truthy: True, all truthy: False


### Do it like this!¶

In :
any_value_truthy = any(example_collection)
all_values_truthy = all(example_collection)
print('any truthy: {}, all truthy: {}'.format(any_value_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 :
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 :
variable = 'John' if some_condition else 'Doe'
print(variable)

John


## Function keywords arguments¶

For better readability and maintainability.

In :
def show_person_details(name, is_gangster, is_hacker, age):
print('name: {}, gangster: {}, hacker: {}, age: {}'.format(
name, is_gangster, is_hacker, 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 :
show_person_details('John Doe', True, False, 83)

name: John Doe, gangster: True, hacker: False, age: 83


### This is much better!¶

In :
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 :
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 :
# original values
a = 1
b = 2

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

2 1


### Do it like this!¶

In :
# original values
a = 1
b = 2

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

2 1


## (Un)packing¶

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


Don't do something like this.

In :
first = my_list
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 :
# 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