【问题标题】:How to type annotate function which iterates over class instances or tuples of same class intances in Python 3?python - 如何在Python 3中键入迭代类实例或相同类实例的元组的注释函数?
【发布时间】:2020-07-23 09:34:09
【问题描述】:

我开始在 Python 3 中使用类型注释进行实验,并且遇到了函数 exclude_filter 的问题,特别是在下面的代码 sn-p 中注释了 items(我发布了这个未注释的)。简而言之,我正在尝试遍历列表并根据某些标准过滤掉一些项目。并且列表中项目的类型是类的实例或这些实例的元组,在这种情况下,我只在元组的第一个成员中寻找标准。

@dataclass
class BaseItem:
    name: str
    something: int

def exclude_filter(items, matches):
    def item_matched(item, matches):
        name = item[0].name if isinstance(item, tuple) else item.name
        for match in matches:
            if match in name:
                return True
        return False    
    items[:] = [i for i in items if not item_matched(i, matches)]

FOOS = [BaseItem("1st foo", 10), BaseItem("2nd foo", 11)]
BARS = [BaseItem("1st bar", 20), BaseItem("2nd bar", 22)]
FOOS_AND_BARS = list(zip(FOOS, BARS))

exclude_filter(FOOS, ["1st"])
exclude_filter(BARS, ["2nd"])
exclude_filter(FOOS_AND_BARS, ["1st"])

print(FOOS)
# [BaseItem(name='2nd foo', something=11)]
print(BARS)
# [BaseItem(name='1st bar', something=20)]
print(FOOS_AND_BARS)
# [(BaseItem(name='2nd foo', something=11), BaseItem(name='2nd bar', something=22))]

我已经尝试了明显错误的items: List[BaseItem] 结果:

Argument 1 to "exclude_filter" has incompatible type "List[Tuple[BaseItem, BaseItem]]"; expected "List[BaseItem]"

所以我试过item: List[Union[BaseItem, Tuple[BaseItem, BaseItem]]]:

Argument 1 to "exclude_filter" has incompatible type "List[BaseItem]"; expected "List[Union[BaseItem, Tuple[BaseItem, BaseItem]]]"

然后我尝试了 T = TypeVar("T", BaseItem, Tuple[BaseItem, BaseItem]) 和 items: List[T] 和 item: T 但我得到了:

"Tuple[BaseItem, BaseItem]" has no attribute "name"

好吧,我尝试了更晦涩的组合,但似乎没有任何效果。注释此代码的正确方法是什么?

【问题讨论】:

  • 我的错,错过了那个。这可能与Why does mypy reject my “mixed union” type declaration?有关
  • 是的,这看起来很相似。但在我的情况下,我不能用Sequence 替换List 来摆脱不变的问题(对应于Dict 和Mapping),因为我在函数体中使用items[:] 语法。它必须是列表。
  • 所以我找到了解决方案但没有解释。如果我使用 TypeVar 并将单行条件表达式 name = item[0].name if isinstance(item, tuple) else item.name 更改为正常的 if-else 块,那么它可以工作。 Mypy 可能无法使用 isinstance() 处理条件表达式。
  • 请注意,显示的类型变量对应于Union[List[BaseItem], List[Tuple[BaseItem, ...]]],而不是List[Union[BaseItem, Tuple[BaseItem, BaseItem]]]。前者不 符合您对 List 的描述,可能有 both 裸和元组嵌套BaseItems。您的示例没有对此进行测试——这个用例是否可取?
  • 在我的情况下,所有列表都应该是同质的,所以TypeVar 解决方案实际上是可以的。有趣的是,List[Tuple[BaseItem, ...]] 与 List[Tuple[BaseItem, BaseItem]] 不兼容,因为存在不变的问题。它适用于Sequence,但不适用于List。此外,List[Tuple[BaseItem, ...]] 确实代表了一个包含所有相同长度元组的列表,或者每个元组都可以有不同的可变长度?我可能会在 mypy github 上开始讨论。

标签: python python-3.x types mypy type-annotation


【解决方案1】:

好吧,我找到了解决这个问题的有效但丑陋的解决方案,遗憾的是它只适用于同质列表。

BaseItemOrTuple = TypeVar(
    "BaseItemOrTuple",
    BaseItem,
    Tuple[BaseItem, BaseItem],
    Tuple[BaseItem, BaseItem, BaseItem],
    Tuple[BaseItem, BaseItem, BaseItem, BaseItem],
    # Et cetera for longer tuples
)

def exclude_filter(items: List[BaseItemOrTuple], matches: Sequence[str]) -> None:
    def item_matched(item: BaseItemOrTuple, matches: Sequence[str]) -> bool:
        if isinstance(item, tuple): #  cannot use one-liner here due to possible mypy bug
            name = item[0].name 
        else:
            name = item.name
        for match in matches:
            if match in name:
                return True
        return False    
    items[:] = [i for i in items if not item_matched(i, matches)]

List 不变的最大问题是:

Argument 1 to "exclude_filter" has incompatible type "List[Tuple[BaseItem, BaseItem]]"; expected "List[Tuple[BaseItem, ...]]"

这就是我不能在TypeVar 中使用Tuple[BaseItem, ....] 的原因,我必须明确说明所有可能的元组长度。如果我使用Sequence 就可以了,但由于items[:] 操作我不能。

mypy 中可能存在条件表达式和使用 isinstance() 的错误,因此我需要使用正确的 if-else 块。

【讨论】:

    猜你喜欢
    • 2020-10-14
    • 1970-01-01
    • 1970-01-01
    • 2012-11-28
    • 2013-06-10
    • 1970-01-01
    • 2015-05-28
    • 2021-08-12
    • 2011-11-14
    相关资源
    最近更新 更多