Recnac

# Python Tricks - Basic - Part 2

• Nov 23, 2019
• 10 Min read
• 2,993 Views
• Nov 23, 2019
• 10 Min read
• 2,993 Views
Data
Python

## Introduction

Editor's note: This guide is part of a series on useful Python tricks. Read more about the series and find links the other guides here.

This is the second of two guides on basic Python tricks. In the first, we learned many useful basic Python tricks. Check it out if you missed it.

In this guide, we will continue to add more basic Python tricks to our arsenal.

## Multiple Assignment

Multiple assignment can assign multiple variables at the same time, and it is useful in multiple initialization or swap.

``````1"""multiple initialization"""
2a, b = 1, 2
3"""swap"""
4a, b = b, a
5# a:2, b:1``````
python

#### Inspiring Example

``````1"""next assignment in linked list"""
2# L206: reverse a linked list
3def reverse(head: ListNode) -> ListNode:
4    prev, cur = None, head
5    while cur:
6        cur.next, cur, prev = prev, cur.next, cur
7    return prev``````
python

### Further Understanding

Multiple assignment is a pack-unpack technique.

``1a, b = b, a``
python

is equivalent to

``````1pack = (b, a)
2a = pack[0]
3b = pack[1]``````
python

From the implementation mechanism, we can see that the so-called "same time" assignment is actually executing sequentially. So be careful with the assignment order in complex scenarios. See the following two examples.

#### Swap Array Elements

Wrong Version:

``````1"""nums[0] has been changed before usage"""
2nums[0], nums[nums[0]] = nums[nums[0]], nums[0]``````
python

Right Version:

``1nums[nums[0]], nums[0] = nums[0], nums[nums[0]]``
python

#### Swap Linked List Nodes

Wrong Version:

``````1"""cur.next has been changed before usage"""
2cur.next, cur.next.next.next, cur.next.next = cur.next.next, cur.next, cur.next.next.next``````
python

Right Version:

``1cur.next.next.next, cur.next.next, cur.next = cur.next, cur.next.next.next, cur.next.next``
python

## Mirror Index

Some programmers may feel confused by the negative index because the positive index starts with 0 while the negative index starts with -1.

If you are confused about `-` and looking for another way to index backward more understandably, you can try mirror index `~`—a mirror of forward. It starts from rightmost with `~0`, which is more unified.

As I said in this post, `~` actually is a math trick of two's complement binary code, and it is easier to understand in certain situations.

``````1"""index start from right to left"""
2arr = ["a", "b", "c", "d"]
3arr[~0], arr[~1]
4# output: ('d', 'c')``````
python

#### Inspiring Examples

Here are the typical usages for mirror index `~`:

``````1"""swap mirror node"""
2def reverse(arr: List[int]) -> None:
3    for i in range(len(arr) // 2):
4        arr[i], arr[~i] = arr[~i], arr[i]
5
6"""find median in a sort list"""
7def median(arr: List[float]) -> float:
8    mid = len(arr) // 2
9    return (arr[mid] + arr[~mid]) / 2
10
11"""deal with mirror pairs"""
12# L246: verify whether a number is strobogrammatic
13# strobogrammatic number looks the same when rotated 180 degrees
14def is_strobogrammatic(num: str) -> bool:
15    return all(num[i] + num[~i] in '696 00 11 88' for i in range(len(num) // 2 + 1)) ``````
python

## return None

Python adds an implicit `return None` to the end of any function. Therefore, if a function doesn't specify a return value, it returns `None` by default.

Put plainly, `return None` equals to `return`, and also equals to no return at all.

#### Inspiring Example

``````1"""no return equals to return None, more concise"""
2# L226: invert a binary tree
3def invert_tree(root: TreeNode) -> TreeNode:
4    if root:
5        root.left, root.right = invertTree(root.right), invertTree(root.left)
6        return root``````
python

## Slice Assignment

When you specify a slice on the left side of the `=` operator, that means slice assignment. Slice assignment is a special syntax for list, where you can insert, delete, or replace old slices from another list in a single operation:

``````1arr = [0, 1, 2, 3, 4, 5]
2
3"""insert"""
4arr[1:1] = [6, 7]
5# arr: [0, 6, 7, 1, 2, 3, 4, 5]
6
7"""delete"""
8arr[1:3] = []
9# arr: [0, 1, 2, 3, 4, 5]
10
11"""replace"""
12arr[1:3] = [6, 7]
13# arr: [0, 6, 7, 3, 4, 5]
14
15"""replace slice with different size"""
16arr[-2:] = [2] * 3
17# arr: [0, 6, 7, 3, 2, 2, 2]
18
19"""replace the entire list"""
20arr[:] = [1, 2, 3]
21# arr: [1, 2, 3]``````
python

#### Inspiring Example

``````1"""replace(or batch assignment) by slice assignment"""
2# L280: reorder unsort array in-place such that nums[0] <= nums[1] >= nums[2] <= nums[3]...
3def wiggle_sort(nums: List[int]) -> None:
4    for i in range(len(nums)):
5        nums[i:i+2] = sorted(nums[i:i+2], reverse=i%2)``````
python

## Maximum/Minimum Integer

If you need something like `Integer.MAX_VALUE` in Java, you can use `float('inf')`, thanks to the dynamic typing of Python.

``````1"""float('inf'), float('-inf') as the initial value of min_val and max_val"""
2min_val, max_val = float('inf'), float('-inf')
3
4for i in range(10):
5    min_val = min(min_val, i)
6    max_val = max(max_val, i)
7# min_val:0, max_val:9``````
python

## for else / while else

According to Transforming Code into Beautiful, Idiomatic Python by Raymond Hettinger, `for else` / `while else` distinguishes multiple exit points in loops. It is a replacement for certain `GOTO` use cases. The more accurate term for `else` here is no break.

In my understanding, this is used when you care about both break and non-break logics and allows you to omit the tracking variable.

``````1found_obj = None
2for obj in objects:
3    if obj.key == search_key:
4        found_obj = obj
5        break
6else:	# no break
7    print('no object found')``````
python

## Emulate switch

Python doesn't have a `switch` keyword, but there are several ways to emulate `switch`. The simple and intuitive way is an 'if-else-if' ladder. However, because of the jump table, a switch statement is much faster than this solution.

A more Pythonic way is to use dictionary mapping (associative array) with `lambda`:

``````1"""emulate switch/case with dict mapping"""
2def op_dict(operator: str, x: float, y: float) -> float:
3     return {
4         '+': lambda: x + y,
5         '-': lambda: x - y,
6         '*': lambda: x * y,
7         '/': lambda: x / y,
8     }.get(operator, lambda: None)()
9
10op_dict('*', 2, 3)
11# output: 6``````
python

In another method, you can use the reflection technique (`getattr`) to dynamically determine which function needs to be called during runtime.

## Decorator

Here is a good explanation of Python's decorator mechanism:

In Python, functions are the first class objects. This means that they support operations such as being passed as an argument, returned from a function, modified, and assigned to a variable.

Decorators are a very powerful and useful tool in Python since they allow programmers to modify the behavior of function or class. Decorators allow us to wrap another function in order to extend the behavior of wrapped function, without permanently modifying it.

#### Inspiring Example

In the following example, we use decorator technique to decorate the Fibonacci function, then enhance it with memoization.

``````1from functools import wraps
2def memoization(func):
3    cache = {}
4    miss = object()
5
6    @wraps(func)
7    def wrapper(*args):
8        result = cache.get(args, miss)
9        if result is miss:
10            result = func(*args)
11            cache[args] = result
12        return result
13
14    return wrapper
15
16@memoization
17def fib(n):
18    if n < 2:
19        return n
20    return fib(n - 1) + fib(n - 2)
21
22fib(10)
23# output: 55``````
python

## Modify While Iteration

Python supports modifying `list` while traversing.

#### Inspiring Example

A suitable usage is to replace `queue` in BFS. This is more concise, but uses more memory. However, for this scenario, you need to record the whole path. Here is a better way.

``````1"""bfs with list, append while iteration"""
2# L582: given n processes, each process has a unique PID (process id) and its PPID (parent process id)
3# kill represents a process you want to kill, return a list of PIDs of processes that will be killed
4def kill_process(pid: List[int], ppid: List[int], kill: int) -> List[int]:
5    d = defaultdict(list)
6    for c, p in zip(pid, ppid):
7        d[p].append(c)
8    bfs = [kill]
9    for i in bfs:
10        bfs += d[i]
11    return bfs``````
python

## Conclusion

In this guide, we have learned more basic Python tricks, such as multiple assignment, slice assignment, and decorator. I hope some of them will be useful for you.

There are other advanced techniques not mentioned here. This guide simply offers a good starting point to travel into the Python world. You can also download an example notebook, basic.ipynb,(https://github.com/recnac-itna/python_tricks/blob/master/basic.ipynb) from Github.

This guide is one of a series of Python tricks guides:

I hope you enjoyed it. If you have any questions, you're welcome to contact me at [email protected].