Функция «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 во многих местах своего кода. В противном случае соотношение затрат и выгод, вероятно, невелико.