【问题标题】:Why does mypy not consider a class as iterable if it has __len__ and __getitem__ but no __iter__如果一个类有 __len__ 和 __getitem__ 但没有 __iter__,为什么 mypy 不认为它是可迭代的
【发布时间】:2020-05-10 13:13:43
【问题描述】:

我在玩 mypy 和一些 Python 的基本迭代,并编写了以下代码库:

from typing import Iterator
from datetime import date, timedelta

class DateIterator:
    def __init__(self, start_date, end_date):
        self.start_date = start_date
        self.end_date = end_date
        self._total_dates = self._get_all_dates()

    def _get_all_dates(self) -> Iterator[date]:
        current_day = self.start_date
        while current_day <= self.end_date:
            yield current_day
            current_day += timedelta(days=1)

    def __len__(self):
        print("Calling the len function...")
        return len(self._total_dates)

    def __getitem__(self, index):
        print(f"Calling getitem with value of index as {index}")
        return self._total_dates[index]

if __name__ == "__main__":
    date_iterator = DateIterator(date(2019, 1, 1), date(2019, 1, 15))
    for new_date in date_iterator:
        print(new_date)

    date_str = ",".join([str(new_date) for new_date in date_iterator])
    print(date_str)

    print(f"Checking the length of the collection {len(date_iterator)}")

    print(f"Checking if indexing works : {date_iterator[4]}")

现在也玩mypy 我遇到了以下问题:

iterator_test_with_getitem.py:30: error: Cannot assign to a type
iterator_test_with_getitem.py:30: error: "DateIterator" has no attribute "__iter__" (not iterable)
iterator_test_with_getitem.py:33: error: "DateIterator" has no attribute "__iter__" (not iterable)
Found 3 errors in 1 file (checked 1 source file)

有人可以指导我,如果一个对象可以通过添加__len____getitem__ 方法进行迭代,那么为什么mypy 在没有iter 方法时会抱怨

也有人可以告诉我第 30 行的问题是什么。我也找不到该错误的任何合乎逻辑的解释。

【问题讨论】:

  • 在此代码上运行 mypy 时,没有看到“无法分配给类型”错误。我怀疑这来自您使用for date in date_iterator 而不是for new_date in date_iterator 的代码版本。

标签: python mypy


【解决方案1】:

Mypy -- 和 PEP 484 类型检查器一般 -- 将可迭代定义为任何定义 __iter__ 方法的类。您可以在 Typeshed 中看到 Iterable 类型的确切定义,标准库的类型提示集合:https://github.com/python/typeshed/blob/master/stdlib/3/typing.pyi#L146

Iterable之所以这样定义,排除了只定义__getitem____len__的类型是因为:

  1. 说一个类型必须实现 __iter__ 或实现 __getitem__/__len__ 的唯一方法是使用一个 Union —— 将 Iterable 定义为一个 union 会使任何想要扩展的人的生活变得有点复杂在自己的代码中使用 Iterable 类型。

  2. 相反,定义__getitem____len__ 的类定义自己的__iter__ 方法是微不足道的。例如,您可以执行以下简单的操作:

    def __iter__(self) -> Iterator[date]:
        for i in range(len(self)):
            yield self[i]
    

    或者,类似这样的东西(假设你修复了你的构造函数,所以self._total_dates 是一个列表,而不是一个生成器):

    def __iter__(self) -> Iterator[date]:
        return iter(self._total_dates)
    

因此,考虑到这种成本效益权衡,将 Iterable 定义为任何实现 __iter__ 的对象是有意义的。对于定义自定义类并简化想要编写函数来操作可迭代对象的人来说,这并不是什么负担。

【讨论】:

  • 使用 range/len 和索引定义自己的迭代器是 slower,而不是让默认值处理它。
  • @user2357112supportsMonica -- 这取决于你如何实现__iter__。确实,我提供的第一个版本速度较慢,因为它所做的只是重新实现 Python 在底层所做的事情,只是在 Python 而不是 C 中。但是,与第二个版本类似的任何东西都比这两种选择都快:因为我们是直接访问底层字段并返回一个生成器,我们可以跳过必须不断调用__getitem__ 函数的开销。例如,参见ideone.com/8WE5Ve——最后四次运行都与第一次相当或更快。
猜你喜欢
  • 1970-01-01
  • 2013-12-31
  • 1970-01-01
  • 2017-03-19
  • 2017-10-11
  • 1970-01-01
  • 1970-01-01
  • 2017-02-09
  • 1970-01-01
相关资源
最近更新 更多