I just recently posted a blog about Annotating functions in Typescript. I just finished doing a bit of a study and understood more on how to annotate functions in Python and this blog is going to be all about annotating Python functions with similar examples to the last blog.
You can validate your type annotations in Visual Studio Code by setting python.analysis.typeCheckingMode
to one of basic, standard, strict,
. basic
and standard
options doesn't necessarily make sure you annotate your functions and variables but strict
does.
Function Values
This might shock you but you can return functions and pass functions as values in Python. Callback functions are actually annotated using the Callable
type which is written like this;
Callable[[argtype1, argtype2, argtype3], returnType]
For instance, a function length(text: str) -> int
will be annotated as Callable[[str], int]
For example;
This function in JavaScript
function multiplier(factor){
return value => factor * value
}
const n = multiplier(6)
n(8) // 48
can be written like this in Python
def multiplier(factor):
def inner(value):
return value * factor
return inner
n = multiplier(6)
n(8) #48
We can create a TypeAlias
called number
which is a Union
(literally) of both an int
and a float
like;
from typing import TypeAlias, Union
number: TypeAlias = Union[int, float]
To approach the parameters as JavaScript numbers.
So therefore, to annotate this function, we have;
def multiplier(factor: number) -> Callable[[number], number]:
def inner(value: number) -> inner:
return value * factor
return inner
a = multiplier(4.5)
a(3) #13.5
Generic functions
The classic generic function example is
def pick(array, index):
return array[index]
pick([1,2,3], 2) #3
Using TypeVar
we can now create generic verbose (more verbose than typescript).
from typing import TypeVar
T = TypeVar("T") # the argument and the name of the variable should be the same
so that we have
from typing import TypeVar, Sequence
def pick(array: Sequence[T], index: int) -> T:
return array[index]
print(pick([1,2,3,4], 2))
So what about a custom myMap
function that acts like map
in JavaScript. such that we have;
Remember:
map()
in Python returns anIterable
type not aList
type
def myMap(array, fn):
return map(fn, array)
def twice(n): return n * 2
print(myMap([1,2,3], twice))
We can use a mixture of Callable
and TypeVar
types to annotate this function. Observe...
from typing import TypeVar, Iterable, Callable
Input = TypeVar("Input") # Input and "Input" must be the same
Output = TypeVar("Output")
def myMap(array: Iterable[Input], fn: Callable[[Input], Output]) -> Iterable[Output]:
return map(fn, array)
def twice(n: int) -> int: return n * 2
print(myMap([1,2,3], twice))
or we can alias the Callable
function
from typing import TypeVar, Iterable, Callable
Input = TypeVar("Input")
Output = TypeVar("Output")
MappableFunction = Callable[[Input], Output]
def myMap(array: Iterable[Input], fn: MappableFunction[Input, Output]) -> Iterable[Output]:
return map(fn, array)
Observe that MappableFunction
takes those generic types Input
and Output
and applies them to the context of Callable[[Input], Output]
.
Take a minute to think of how the myFilter
function will be annotated?
Well if you thought of this
from typing import Iterable, TypeVar, Callable
Input = TypeVar("Input")
def myFilter(array: Iterable[Input], fn: Callable[[Input], bool]) -> Iterable[Input]:
return filter(fn, array)
You're right
Generic Classes
I know I'm not supposed to be talking about class annotation but give me some time to explain generic classes.
If you came from the Typescript-verse, this was how you would define them
class GenericStore<Type>{
stores: Array<Type> = []
constructor(){
this.stores = []
}
add(item: Type){
this.stores.push(item)
}
}
const g1 = new GenericStore<string>(); //g1.stores: Array<string>
g1.add("Hello") //only string are allowed
But in Python they are rather different and awkward.
- First we import the
Generic
type, then we make them the child of the Generic class
So to recreate this GenericStore
class in Python
from typing import Generic, TypeVar
from dataclasses import dataclass
Type = TypeVar("Type")
@dataclass
class GenericStore(Generic[Type]):
store: list[Type] = []
def add(self, item: Type) -> None:
self.store.append(item)
g1 = GenericStore([True, False]) #g1.store: list[bool]
g1.add(False) # only bool is allowed
Why should I learn how to annotate functions in Python?
As I have said in the former blog, It helps in building a much smarter type system which in turn reduces your chances of bugs (especially when using static file checkers like mypy
). Also when writing libraries (or SDKs) using a robust type system can improve the productivity of the developer using the library by a margin (mostly because of editor suggestions)
If you have any questions or there are mistakes in this writing, feel free to share them in the comments below ⭐
Top comments (0)