Функция «find» Python PyDash возвращает Any вместо переданного типа данных

Логика в моем случае намного сложнее, но вот к чему она сводится.
У меня есть список экземпляров класса данных.

      import pydash as pyd

@dataclass
class Info:
    color: str
    taste: str

@dataclass
class Fruit:
    id: int
    name: str
    info: Info

fruits = [
    Fruit(1, 'apple', Info('red', 'sweet')),
    Fruit(2, 'orange', Info('orange', 'sour')),
    Fruit(3, 'banana', Info('yellow', 'sweet')),
]

По этому списку я провожу поиск.
Когда я обращаюсь к экземпляру напрямую, предложения атрибутов и подсказки типов работают.

      first_fruit_color = fruits[0].info.color
#                                   ^^
#                          (variable) color: str
print(first_fruit_color) # red

Но когда я использую метод «find» PyDash (это как Lodash в JavaScript), он возвращает «любой» тип независимо от того, что передается внутри.
Таким образом, предложения и подсказки типов вообще не работают.

      orange_fruit = pyd.find(fruits, {'info': {'color': 'orange'}})
orange_fruit_name = orange_fruit.name if orange_fruit else None
#                                 ^^
#                          id: Any | Unknown
print(orange_fruit_name) # orange

Можно ли это исправить разумным образом, или я должен написать свою собственную функцию поиска?
Есть ли лучшие библиотеки для таких вещей в Python?

1 ответ

Они не аннотировали свой код и не создавали заглушки типов. Некоторое время назад в этом выпуске была запрошена поддержка типизации Python . Я не знаю о прогрессе в этом.

Это означает, что вы не получите (почти) никаких автоматических предложений от вашей IDE, потому что в принципе вывод типа невозможен.

Можно ли это исправить разумным образом [...]?

Зависит от того, что вы подразумеваете под разумным.

Если это простофункция, которая вас интересует, и, возможно, несколько других, вы можете потратить время на то, чтобы написать для них свои собственные оболочки с соответствующими аннотациями типов или, по крайней мере, с аннотациями/перегрузками, которые точно охватывают ваши варианты использования.

Я быстро накидал вот такую ​​обертку дляfindдля вас, просто чтобы продемонстрировать, как это может выглядеть. Функция сложнее, чем может показаться на первый взгляд, поэтому я не претендую на то, что эти аннотации являются исчерпывающими. Тем не менее, они должны охватывать несколько предполагаемых применений, и они определенно помогут с вашим конкретным примером.

Вот обертка:

      from collections.abc import Callable, Collection, Hashable, Mapping
from typing import Optional, TypeVar, Union, overload

import pydash as pyd  # type: ignore[import]


K = TypeVar("K", bound=Hashable)
T = TypeVar("T")


@overload
def find(
    collection: Mapping[K, T],
    predicate: Optional[Callable[[T, K], bool]] = None,
) -> Optional[T]:
    ...


@overload
def find(
    collection: Collection[T],
    predicate: Union[
        Callable[[T], bool],
        Mapping[str, object],
        T,
        None,
    ] = None
) -> Optional[T]:
    ...


def find(
    collection: Collection[T],
    predicate: Union[
        Callable[[T, K], bool],
        Callable[[T], bool],
        Mapping[str, object],
        T,
        None,
    ] = None,
) -> Optional[T]:
    return pyd.find(collection, predicate=predicate)  # type: ignore[no-any-return]

Применительно к вашему примеру кода это будет выглядеть так:

      ...  # import the custom `find` function

from dataclasses import dataclass


@dataclass
class Info:
    color: str
    taste: str


@dataclass
class Fruit:
    id: int
    name: str
    info: Info


fruits = [
    Fruit(1, 'apple', Info('red', 'sweet')),
    Fruit(2, 'orange', Info('orange', 'sour')),
    Fruit(3, 'banana', Info('yellow', 'sweet')),
]

apple = find(fruits, {'info': Info('red', 'sweet')})
orange = find(fruits, {'info': {'color': 'orange'}})
banana = find(fruits, lambda fruit: fruit.id == 3)

reveal_type(apple)
reveal_type(orange)
reveal_type(banana)

Теreveal_typeзвонки предназначены для .

Бегmypyнад этим я получаюRevealed type is "Union[Fruit, None]"во всех трех случаях.

Обратите внимание, чтоpydash.findфункция никогда не может гарантировать, что вывод не будет , поэтому вам все равно придется выполнять сужение типа (например, черезassert apple is not Noneилиif orange is not None: ...), чтобы убедить программу проверки типов в том, что вы определенно имеете дело сFruitпример.

В качестве альтернативы вы, конечно, можете заставить свою оболочку вызывать исключение, еслиpyd.findвозвращаетсяNone. Затем вы можете аннотировать его возвращение какTвместоOptional[T]. Зависит от того, какое поведение вы хотите.

Кстати, первая перегрузка охватывает использование сопоставления, такого как словарь. В этом случае вызываемый предикат должен принимать значение и ключ в качестве аргументов (по какой-то причине именно в таком порядке...). Итак, вы также можете сделать что-то вроде этого:

      a = {"x": 1, "y": 2, "z": 3}

def key_is_x(_val: object, key: Hashable) -> bool:
    return key == "x"

x = find(a, key_is_x)
reveal_type(x)  # builtins.int | None

В любом случае, это, вероятно, имеет смысл только в том случае, если вы используете всего несколько функций pydash во многих местах своего кода. В противном случае соотношение затрат и выгод, вероятно, невелико.

Другие вопросы по тегам