pythonpython-typingpylance

Type hint warning `"keys" is not a known attribute of "None"` for `Optional` in Pylance


Recently Pylance has started to add red underlines in my code. I've managed to fix most of it, but I have problems with Optional typing:

def realms(testenv:str, active:bool=True) -> Optional[Dict]:
    """ return a dict of active/inactive realms with info from keycloak, on error return None """
    dat = api(testenv, endpoint="/admin/realms")
    :

all_realms = list(realms(testenv, active=False).keys())
                                                ------

Pylance says:

"keys" is not a known attribute of "None"

If I change the def prototype to:

def realms(testenv:str, active:bool=True) -> Dict:

I get small issues with return None I can fix with raise exceptions, or just remove it.

The code workaround can be a two-liner:

all_realms_dict = realms(testenv, active=False)
all_realms = list(all_realms_dict.keys()) if all_realms_dict else []

Should I stop using Optional and switch to raising exceptions? Is returning None a bad idea?


Solution

  • Since realms() can return a dict or None, Pylance is correct to point out that in case it ever returns None you will be in trouble due to calling .keys() on it. So you have three options:

    1. Always return a dict (get rid of the Optional in the return type) and on error return an empty dict {} but then there will be no difference between an error and an empty result, which might be a concern.

    2. Always return Dict but raise an exception in realms() on errors and catch it in the caller (probably the most Pythonic way)

    3. Keep returning None on errors but check for it in the calling function before doing .keys() on it.

    Example for option 3:

    r = realms(testenv, active=False)
    if r is None:
        # handle error somehow
    else:
        all_realms = list(r.keys())
    

    If the error case is an unexpected condition which would not come up in normal operation I would go for raising an exception.