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?

Asked By: Garrett

||

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.

Answered By: bfvtv vxyfbd

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

Answered By: labroid

@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)
Answered By: Airistotal

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]
Answered By: Garrett

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.

Answered By: RatherBKnitting
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.