It is very common to reference a variable that has not been created yet. It is known as a UnboundLocalError
from typing import Optional
def some_db_call(arg: int) -> str:
return "some_db_call"
def func(arg: Optional[int]) -> int:
if arg:
result = some_db_call(arg)
if result:
variable = 1
else:
variable = 0
return variable
func(None)
# Runtime error of UnboundLocalError
This is what every coder is used to. The problem is that mypy won't catch the possible error of UnboundLocalError. See python/mypy#2400. Pycharm will sometimes catch it but not always with nested ifs.
Style 2 - A default variable
def func(arg: Optional[int]) -> int:
variable = 0
if arg:
result = some_db_call(arg)
if result:
variable = 1
else:
variable = 0
return variable
func(None) # ok
This is a possible solution. One problem is that we still have nested if blocks. Which makes it different to read the happy path logic. The variable also gets assigned in 3 different places. With nested if blocks, the reviewer must check to have that default variable declared. Also, its not refactor friendly, as we can easily delete default variable and then we are back to the problems of style 1.
def func(arg: Optional[int]) -> int:
result = some_db_call(arg) if arg else ""
variable = 1 if result else 0
return variable
This is what i suggest and mypy will typecheck it correctly. I find it easier to understand the happy path. We avoid returning early, and avoid complex if statements. The catch is that it takes some time getting used to. And you'll need to "prove" that the variable you are accessing is not None repeatedly. We can still keep if statements when they are simple. And whenever we really need to mutate