Ошибки и решения#

Ошибки при использовании type вместо isinstance#

Примечание

Это известная проблема https://github.com/python/mypy/issues/4445

При использовании type для создания условия по типу данных таким образом:

from netmiko import ConnectHandler
from typing import Dict, Union, Iterable, Any


def send_show_commands(
    device_params: Dict[str, Any], commands: Union[str, Iterable[str]]
) -> Dict[str, str]:
    result = {}
    if type(commands) == str:
        commands = [commands]
    with ConnectHandler(**device_params) as ssh:
        ssh.enable()
        for command in commands:
            output = ssh.send_command(command)
            result[command] = output
    return result

Mypy выдаст такую ошибку:

$ mypy example_16_isinstance.py
example_16_isinstance.py:11: error: List item 0 has incompatible type "Union[str, Iterable[str]]"; expected "str"
Found 1 error in 1 file (checked 1 source file)

Исправить ситуацию можно используя isinstance вместо type:

def send_show_commands(
    device_params: Dict[str, Any], commands: Union[str, Iterable[str]]
) -> Dict[str, str]:
    result = {}
    if isinstance(commands, str):
        commands = [commands]
    with ConnectHandler(**device_params) as ssh:
        ssh.enable()
        for command in commands:
            output = ssh.send_command(command)
            result[command] = output
    return result

Отсутствие проверки None#

Пример кода:

import re
from typing import List, Tuple


def parse_sh_cdp_neighbors(command_output: str) -> List[Tuple[str, ...]]:
    regex = re.compile(
        r"(?P<r_dev>\w+) +(?P<l_intf>\S+ \S+)"
        r" +\d+ +[\w ]+ +\S+ +(?P<r_intf>\S+ \S+)"
    )
    connect_list = []
    match_l_dev = re.search(r"(\S+)[>#]", command_output)
    l_dev = match_l_dev.group(1)
    for match in regex.finditer(command_output):
        neighbor = (l_dev, *match.group("l_intf", "r_dev", "r_intf"))
        connect_list.append(neighbor)
    return connect_list

При таком коде mypy выдаст ошибку:

$ mypy example_17_re_search.py
example_17_re_search.py:13: error: Item "None" of "Optional[Match[str]]" has no attribute "group"
Found 1 error in 1 file (checked 1 source file)

Ошибка возникает потому что выражение match_l_dev = re.search(r"(\S+)[>#]", command_output) может возвращать None или объект Match. Без проверки что возвращается именно истинное значение, будет ошибка. Код надо исправить таким образом:

def parse_sh_cdp_neighbors(command_output: str) -> List[Tuple[str, ...]]:
    regex = re.compile(
        r"(?P<r_dev>\w+) +(?P<l_intf>\S+ \S+)"
        r" +\d+ +[\w ]+ +\S+ +(?P<r_intf>\S+ \S+)"
    )
    connect_list = []
    match_l_dev = re.search(r"(\S+)[>#]", command_output)
    if match_l_dev:
        l_dev = match_l_dev.group(1)
    for match in regex.finditer(command_output):
        neighbor = (l_dev, *match.group("l_intf", "r_dev", "r_intf"))
        connect_list.append(neighbor)
    return connect_list

Особенности работы с Union#

Пример кода, в котором в значении словаря типы указаны как Union[str, int, bool] (полный пример в файле example_14_dict_multiple_types_wrong.py):

def send_show_command_to_devices(
    devices: List[Dict[str, Union[str, int, bool]]], command: str
) -> Dict[str, str]:
    data = {}
    for device in devices:
        output = send_show_command(device, command)
        data[device["host"]] = output
    return data

В этом случае возникнет такая ошибка:

$ mypy example_14_dict_multiple_types.py
example_14_dict_multiple_types_wrong.py:24: error: Incompatible return value type (got "Dict[Union[str, int, bool], str]", expected "Dict[str, str]")
Found 1 error in 1 file (checked 1 source file)

Проблема связана с тем, что если в значении словаря указан Union[str, int, bool], то mypy это воспринимает как то, что любое значение может быть любым из этих типов. Указав что результатом будет словарь Dict[str, str]. Мы как бы уточняем, что device["host"] соответствует именно строка, но при работе с Union это будет ошибкой. Исправить ошибку можно либо указав, что возвращаемый словарь будет содержать в ключе Union[str, int, bool], или указав в словаре в devices тип значения Any (полный пример в example_14_dict_multiple_types.py):

def send_show_command_to_devices(
    devices: List[Dict[str, Any]], command: str
) -> Dict[str, str]:
    data = {}
    for device in devices:
        output = send_show_command(device, command)
        data[device["host"]] = output
    return data