Dealing with lack of non-null assertion operator in Python
Question:
I would like to allow Mypy’ strict_optional
flag. However, consider this:
emails = [get_user(uuid).email for uuid in user_uuids]
where get_user
could return None
in theory, but in this use case, I know it can’t (and am fine with getting an exception if it did). This would have to become:
emails = []
for uuid in user_uuids:
user = get_user(uuid)
assert user is not None
emails.append(user.email)
In TypeScript, there’s a non-null assertion operator which would allows you to just add a !
(as in getUser(uuid)!.email
).
Is there any better or more elegant way to handle this problem?
Answers:
Python and some other langauges feature short-circuiting so the following statement is perfectly fine
TestArr = [None, None, None, None]
ArrTest = [x.testFunc() for x in TestArr if x != None]
print(ArrTest)
and will return a blank list.
Although I’m unfamiliar with strict_optional
.
There is no reason you can’t use the same call in the conditional, so
emails = [get_user(uuid).email for uuid in user_uuids if get_user(uuid)]
will work
@labroid has a good answer. One comment mentions that it’s not ideal to call get_user twice, so I’ll just build on labroid’s answer to create a statement that only calls get_user once:
users = [
{"uuid":"abc", "email":"email"}
]
def get_user(uuid):
for user in users:
if user["uuid"] == uuid:
return user
return None
user_uuids = ["abc", "def"]
emails = [user["email"] for user in [get_user(uuid) for uuid in user_uuids] if user != None]
print(emails)
I found two ways that I think get close to a non-null assertion operator and therefore cast type Optional[User]
to User
:
1) Use typing.cast
from typing import cast
emails = [cast(User, get_user(uuid)).email for uuid in user_uuids]
2) Imitate non-null assertion with function
from typing import TypeVar
T = TypeVar('T')
def not_none(obj: Optional[T]) -> T:
assert obj is not None
return obj
emails = [not_none(get_user(uuid)).email for uuid in user_uuids]
Since I wouldn’t want to call get_user twice. I would do something like this:
users = map(get_user, user_uuids)
emails = [user.email for user in users if user]
This feels pythonic more than anything else I’ve seen, to me.
I would like to allow Mypy’ strict_optional
flag. However, consider this:
emails = [get_user(uuid).email for uuid in user_uuids]
where get_user
could return None
in theory, but in this use case, I know it can’t (and am fine with getting an exception if it did). This would have to become:
emails = []
for uuid in user_uuids:
user = get_user(uuid)
assert user is not None
emails.append(user.email)
In TypeScript, there’s a non-null assertion operator which would allows you to just add a !
(as in getUser(uuid)!.email
).
Is there any better or more elegant way to handle this problem?
Python and some other langauges feature short-circuiting so the following statement is perfectly fine
TestArr = [None, None, None, None]
ArrTest = [x.testFunc() for x in TestArr if x != None]
print(ArrTest)
and will return a blank list.
Although I’m unfamiliar with strict_optional
.
There is no reason you can’t use the same call in the conditional, so
emails = [get_user(uuid).email for uuid in user_uuids if get_user(uuid)]
will work
@labroid has a good answer. One comment mentions that it’s not ideal to call get_user twice, so I’ll just build on labroid’s answer to create a statement that only calls get_user once:
users = [
{"uuid":"abc", "email":"email"}
]
def get_user(uuid):
for user in users:
if user["uuid"] == uuid:
return user
return None
user_uuids = ["abc", "def"]
emails = [user["email"] for user in [get_user(uuid) for uuid in user_uuids] if user != None]
print(emails)
I found two ways that I think get close to a non-null assertion operator and therefore cast type Optional[User]
to User
:
1) Use typing.cast
from typing import cast
emails = [cast(User, get_user(uuid)).email for uuid in user_uuids]
2) Imitate non-null assertion with function
from typing import TypeVar
T = TypeVar('T')
def not_none(obj: Optional[T]) -> T:
assert obj is not None
return obj
emails = [not_none(get_user(uuid)).email for uuid in user_uuids]
Since I wouldn’t want to call get_user twice. I would do something like this:
users = map(get_user, user_uuids)
emails = [user.email for user in users if user]
This feels pythonic more than anything else I’ve seen, to me.