Search code examples
pythonvisual-studio-codetype-hintingpython-typingpylance

Python VS Code: Type hinting with multiple types


I am using python type hinting in VS Code.

The user_wildcard variable can either be a str="*.txt", or bool=False.

No problems here, python can run the code fine. However, VS Code shows a "problem with my code" although there is no problem.

Is there are more "correct" way if type hinting so that I wouldn't get this problem in VS code?

screenshot of VS code inline type hinting

import logging
class FileManager:
    def __init__(self):
        self.log = logging.getLogger(__name__)
        self.cwd = Path(__file__).parent

    def go_up(self):
        self.cwd = self.cwd.parent
        return self

    def get_cwd(self) -> Path:
        return self.cwd

    def cd(self, foldername: str):
        path = self.cwd / foldername
        if path.is_dir():
            self.cwd = path
            return self
        else:
            self.log.error(f"invalid {foldername=}")

    def find_file(self, filename: str, single_file: bool=False) -> list | Path:
        filepaths = self.cwd.glob(filename)
        if single_file:
            return next(filepaths)
        else:
            return [x for x in filepaths]

    def get_resource_file(self, use_wildcard: str | bool = False):
        self.go_up().cd("resources")
        if use_wildcard:
            return self.find_file(use_wildcard, single_file=True)
        else:
            raise NotImplementedError(f"{use_wildcard=}")

I read that support for Union is added from python>3.10, which eliminates the need for from typing import Union.

However, I have tried using Union but it shows the same problem as well.

screenshot of using Union instead


Solution

  • The problem is that the find_file method only expects its first argument filename to be a str. But inside get_resource_file the use_wildcard argument can be str or bool. So calling self.find_file(use_wildcard) is not type safe.

    You can establish some sort of type guard before calling find_file to ensure that by that time use_wildcard is definitely a str (and cannot be a bool anymore). The simplest way is to check if isinstance(use_wildcard, str) instead of your current if use_wildcard.

    Additionally, if you really only want to allow use_wildcard to be a str or the literal False (not any bool, i.e. not True), you can annotate that method using the typing.Literal type as str | Literal[False]. In that case the check if use_wildcard is technically sufficient because anything "truthy" will necessarily be a (non-empty) str:

    from pathlib import Path
    from typing import Any, Literal
    
    
    class FileManager:
        def find_file(self, filename: str, single_file: bool = False) -> list[Any] | Path:
            ...
    
        def get_resource_file(self, use_wildcard: str | Literal[False] = False):
            ...
            if use_wildcard:
                return self.find_file(use_wildcard, single_file=True)
            else:
                raise NotImplementedError(f"{use_wildcard=}")
    

    But even then I would recommend an explicit check. Though your semantics seems a bit off here anyway, but I assume the code is just not fully worked out yet.