Python tips: type safety and operator
I was pretty confident that python is probably the best programming language for interview six years ago. My recent interview experience confirmed that claim. Python is succinct, battery-included; making it a perfect programming language in the live code session. I would like to share some new tips learned in my recent job hunting.
Type Safety
The type safety has been widely accepted in the python community despite that the original typing system was built upon duck typing. I take a pragmatic approach: always enforce the strong type until it breaks.
Assume a function compute takes an action to execute math operations:
from typing import Literal
ActionType = Literal["add", "subtract"]
def compute(action: ActionType, *args):
print(action)
The Literal provides the static type checking, but will not
prevent the consumer to invoke compute("nop").
A better option, arguably, is to use enum.Enum to limit the value
of action:
from enum import Enum
class ActionType(Enum):
ADD = 'add'
SUBTRACT = 'subtract'
def compute(action: ActionType, *args):
print(action.value)
compute(ActionType.ADD)
Dive into Enum
Bonus point: we can bind the compute function to the ActionEnum
like vtable does:
import operator
from functools import reduce
class ActionEnum(Enum):
ADD = lambda args: sum(args)
SUBTRACT = lambda args: reduce(operator.sub, args)
def compute(action: ActionEnum, *args):
f = action
return f(args)
print(compute(ActionEnum.ADD, 1, 2, 3)) # 6
print(compute(ActionEnum.SUBTRACT, 1, 2, 3)) # -4
Do you notice the ActionEnum.ADD is callable, while ActionType.ADD is not. The
inspection shows ActionEnum.ADD is a function!
[f"{x}: iscallable={callable(x)}, type={type(x)}" for x in (ActionType.ADD, ActionEnum.ADD)]
["ActionType.ADD: iscallable=False, type=<enum 'ActionType'>",
"<function ActionEnum.<lambda> at 0x10a3c9440>: iscallable=True, type=<class 'function'>"]
ActionEnum is NOT even enumerable!
[f"{x}: members={[m for m in x]}" for x in (ActionType, ActionEnum)]
["<enum 'ActionType'>: members=[<ActionType.ADD: 'add'>, <ActionType.SUBTRACT: 'subtract'>]",
"<enum 'ActionEnum'>: members=[]"]
I am no sure whether this behavior is by design, or purely coincidence.
Prefer operators over lambda
You have encountered the operator.sub in the previous section, which provides
a higher abstraction than the lambda function and make the code more readable;
— and in the AI era, more token efficient 😅.
attrgetterto access attributeitemgetterto access key from dict-like objectitemgetterto access the element for the specific index
For example, we can sort by the value attribute of Enum as:
sorted(AnyType, key=operator.attrgetter('value'))
[<ActionType.ADD: 'add'>, <ActionType.SUBTRACT: 'subtract'>]
And sort the students by names, also group by the department:
sorted(students, key=operator.itemgetter('department', 'name'))
Hidden gems
Besides the standard libraries we have discussed before, there are more hidden gems I found, updating …
bisecthandles the binary search’s boundary check nicely.io.StringIOis quite handy to test functions interacting with files.