In Python we often have a situation where the default argument for some input parameter is None
and, if that is the case, we immediately initialize that variable at the top of the function body for use in the rest of the function. One common use case is for mutable default arguments:
def foo(input_list = None):
if input_list is None:
input_list = []
input_list.append('bar')
print(input_list)
What would be the appropriate way to type hint this?
from typing import List
def foo(input_list: List = None):
if input_list is None:
input_list = []
input_list.append('bar')
print(input_list)
or
from typing import List, Optional
def foo(input_list: Optional[List] = None):
if input_list is None:
input_list = []
input_list.append('bar')
print(input_list)
The core of the question is this: From the caller's perspective it is ok to pass in either a list or nothing. So from the callers perspective the input_list
parameter is Optional
. But within the body of the function, it is necessary that input_list
is indeed a List
and not None
. So is the Optional
flag meant to be an indicator to the caller that it is optional to pass that parameter in (in which case the second code block would be correct and the type hint in the first block would be too restrictive), or is it actually a signal to the body of the function that input_list
may be either a List
or None
(in which case the second code block would be wrong since None
is not acceptable)?
I lean towards not including Optional
because users aren't really supposed to pass anything other than a list into input_list
. It's true that they can pass in None
, and nothing bad will happen. But that's not really the intent.
Note that I am aware that in at least some cases param: type = None
will be parsed as if it was equal to param: Optional[type] = None
. That doesn't actually give me clarity on how I should use the Optional
annotation.
Depending on the type checker you're using and how it's configured, assigning a parameter a default value of None
implicitly makes it Optional
without you having to say so.
Within the function body the type should automatically be narrowed by the if
and the resulting assignment.
Both of these behaviors can be demonstrated by adding reveal_type
statements and running mypy
:
def foo(input_list: list[str] = None):
reveal_type(input_list) # Revealed type is "Union[builtins.list[builtins.str], None]"
if input_list is None:
input_list = []
reveal_type(input_list) # Revealed type is "builtins.list[builtins.str]"