Python Tips

Posted on Wednesday, Jul 01, 2020 in programming

A collection of small Python scripts and tips.

This post is based on a Twitter thread I started in April 2020 and works as a centralized way to read all the tips in an easier format than Twitter's 280 characters.

Both will be updated frequently.

List Flatten without explicit loops

  • Using Itertools' chain
1
2
3
4
5
6
import itertools

test = [[-1, -2], [30, 40], [25, 35]]
list(itertools.chain.from_iterable(test))

>>  [-1, -2, 30, 40, 25, 35]
1
2
3
4
test =  [[-1, -2], [30, 40], [25, 35]]
map(int, ''.join(c for c in test.__str__() if c not in '[]').split(',') )

>> [-1, -2, 30, 40, 25, 35]

Not pretty in my opinion ;)

Count individual items of any iterable

1
2
3
4
5
6
7
8
from collections import Counter
count = Counter(['a', 'b', 'c', 'a', 'a', 'b', 'd'])

print(count)
>> Counter({'a': 3, 'b': 2, 'c': 1, 'd': 1})

count['a']
>> 3

Repeat a series of values from any iterable

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import itertools as it

data = it.cycle([1, 2])

for i in range(10):
    print(next(data))

>> 1
2
1
2
1
2
1
2
1
2

Name slices to reuse them

1
2
3
4
5
6
7
8
# slice(start, end, step)

STEPTWO = slice(None, None, 2)
integer_list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

integer_list[STEPTWO]

>> [0, 2, 4, 6, 8]

This is the same as:

1
integer_list[::2]

Reverse any "indexable" collection that supports slices

1
2
3
4
5
6
# slice(None, None, -1) or [::-1]

integer_list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
integer_list[::-1]

>> [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

Use array to keep homogeneous type of objects in your lists

1
2
3
4
5
6
from array import array

integer_list = array('i', [1, 2, 3])

integer_list = array('i', [1, 2, "a"])
>> TypeError: an integer is required (got type str)

The constructor of array includes a type code parameter and an optional initial list.

Swap dictionary key-values using zip

Using zip:

1
2
3
4
5
6
7
8
9
data = { 'a': 1, 'b': 2, 'c': 3, 'd': 4 }

data.items()
>> dict_items([('a', 1), ('b', 2), ('c', 3), ('d', 4)])

z = zip(data.values(), data.keys())

dict(z)
>>  { 1: 'a', 2: 'b', 3: 'c', 4: 'd' }

Using dictionary comprehensions:

1
2
3
4
data = { 'a': 1, 'b': 2, 'c': 3, 'd': 4 }

{ v:k for k, v in data.items() }
>>  { 1: 'a', 2: 'b', 3: 'c', 4: 'd' }

If there are repeated values, the result will be overwritten, so be careful.

Namedtuples as lightweight, inmutable, record-like objects

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
from collections import namedtuple

Person = namedtuple("Person", "name age gender")
bob = Person("Bob", 30, "male")

bob.age
>> 30

bob[1]
>> 30

Something similar can be achieved using Dataclasses (for Python 3.8+)

Built-ins set and frozenset for unordered collections of hashable objects

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
A = set([1, 2, 3])
B = set([3, 4, 5])

A.union(B)
>> {1, 2, 3, 4, 5}

A.intersection(B)
>> {3}

A.symmetric_difference(B)
>> {1, 2, 4, 5}

frozenset has the same interface but returns an inmutable set object.

Itertools' dropwhile and takewhile to filter from an iterator

1
2
3
4
5
6
7
8
from itertools import  dropwhile

numbers = [-2, -1, 0, 1, 2]

f = lambda x: x < 1

list ( dropwhile (f, numbers) )
>>  [1, 2]

takewhile takes the items if True. The difference with built-in function filter is that the iteration stops whenever the test-function is false.

1
2
3
4
numbers = [-2, -1, 0, 1, 2, -3, -4]
list(dropwhile(f, numbers))

>> [1, 2, -3, -4]

As @jgomo3 Observes, one way to think about those functions is that they simply divide a sequence in halves.

takewhile gives you the first part. dropwhile the last part.

Traspose a matrix with List Comprehensions

1
2
3
4
5
6
7
8
M = [[1,2,3],
     [4,5,6],
     [7,8,9]]

MT = [[row[i] for row in M] for i in range(len(M))]

print(MT)
>> [[1, 4, 7], [2, 5, 8],  [3, 6, 9]]

Also, NumPy provides methods to for easier matrix manipulation.

Datetime to UTC

To create timezone aware datetimes in Python:

1
2
3
4
5
6
7
from datetime import datetime, timezone, timedelta

tz = timezone(timedelta(hours=-6))  # UTC-6
local = datetime(2020, 4, 16, 13, 40, 0, 0, tzinfo=tz)

local.isoformat()
>> '2020-04-16T13:40:00-06:00'
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
from datetime import datetime, timezone

# assuming `local` is a datetime object

print(local.isoformat() )
>> '2020-04-16T13:40:00-06:00'

local_in_utc = local.astimezone(timezone.utc)

print(local_in_utc.isoformat())
>> '2020-04-16T19:40:00+00:00'

You can always use pytz to handle timezones.

1
2
3
4
5
6
7
from datetime import datetime
from pytz import timezone

dt = datetime(2020, 4, 16, 13, 40, 0, tzinfo=pytz.utc)

print(dt.isoformat())
>> '2020-04-16T13:40:00+00:00'

functools.singledispatch to achieve parametric polymorphism in Python

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
from functools import singledispatch


@singledispatch
def process(num=None):
    raise NotImplementedError("Implement process function.")

@process.register(int)
def sub_process(num):
    # processing interger
    return f"Integer {num} has been processed successfully!"

@process.register(float)
def sub_process(num):
    # processing float
    return f"Float {num} has been processed successfully!"

# use the function
print(process(12.0))
print(process(1))