【问题标题】:Python typing for a subclass of list列表子类的 Python 类型
【发布时间】:2019-02-27 20:30:25
【问题描述】:

我希望能够定义列表子类的内容必须是什么。该类如下所示。

class A(list):
   def __init__(self):
      list.__init__(self)

我想包括打字,这样会发生以下情况。

import typing

class A(list: typing.List[str]):  # Maybe something like this
   def __init__(self):
      list.__init__(self)

>> a = A()
>> a.append("a")  # No typing error
>> a.append(1)  # Typing error

【问题讨论】:

  • 您需要泛型类型。请注意,python never 强制执行您的类型注释。您可以使用第三方静态类型检查器,例如 mypy 虽然
  • 基本上你需要自己实现MutableSequence protocol。我不会list 继承,因为A 应该可以在普通list 所在的任何地方使用,这意味着可能接受myAobject.append("foo") 之类的东西。让A 包含一个list 作为实例变量,而不是从list 继承,并让定义的方法强制执行int-only 限制。
  • (抱歉,我在之前的评论中考虑了整数列表。)
  • @chepner 但是如果我想在我的子类中使用与 list 相同的方法,为什么不直接使用 list 呢?
  • @Interlooper 说A 应该是ints 的列表。 Liskov 替换原则说,在任何我想使用list 的实例的地方,我都应该能够使用A 的实例来代替它。但如果A (例如)应该是ints 的列表,则意味着以下内容无效:a = A(); a.append("foo")list 是任意对象的可变序列; A 不是,所以它不应该是list 的子类。两个类是否具有相同的接口并不重要,因为语义是非常不同的。

标签: python python-3.x list subclass python-typing


【解决方案1】:

typing 方便地提供了collections.MutableSequence 的通用版本,因此具有以下效果:

import typing

T = typing.TypeVar('T')
class HomogeneousList(typing.MutableSequence[T]):
    def __init__(self, iterable: typing.Iterable[T]=()) -> None:
        self._data: typing.List[T]  = []
        self._data.extend(iterable)

    @typing.overload
    def __getitem__(self, index: int) -> T: ...
    @typing.overload
    def __getitem__(self, index: slice) -> HomogeneousList[T]: ...
    def __getitem__(self, index):
        return self._data[index]

    @typing.overload
    def __setitem__(self, index: int,  item: T) -> None: ...
    @typing.overload
    def __setitem__(self, index: slice, item: typing.Iterable[T]) -> None: ...
    def __setitem__(self, index, item):
        self._data[index] = item

    def __delitem__(self, index: typing.Union[int, slice]) -> None:
        del self._data[index]

    def __len__(self) -> int:
        return len(self._data)

    def insert(self, index: int, item: T) -> None:
        self._data.insert(index, item)


string_list = HomogeneousList[str]()
string_list.append('foo')
string_list.append(42)


int_list = HomogeneousList[int]()
int_list.append(42)
int_list.append('foo')

现在,mypy给出以下错误:

test.py:36: error: Argument 1 to "append" of "MutableSequence" has incompatible type "int"; expected "str"
test.py:41: error: Argument 1 to "append" of "MutableSequence" has incompatible type "str"; expected "int"

输入__getitem__ 等有一些棘手的方面,因为它们也接受slice 对象,但并不可怕。

注意,这很有用,因为如果你只是尝试这样做:

class HomogeneousList(collections.abc.MutableSequence, typing.Generic[T]):
    ....

MyPy 至少不会为 append 抛出错误。 AFAIKT 你必须明确添加:'

def append(self, item: T) -> None:
    self._data.append(item)

首先删除了collections.abc.MutableSequence 的许多实用程序。无论如何,谢天谢地,打字提供了所有这些开箱即用的通用版本!

注意,您可以像我展示的那样通用地使用这些,但您也可以执行以下操作:

class StringList(HomogeneousList[str]):
    pass

mylist = StringList([1,2,3]) # mypy error
mylist = StringList('abc') # no error

mylist.append('foo') # no error
mylist.append(42) # mypy error

【讨论】:

    【解决方案2】:

    在 Python 3.9 之前,您可以使用:

    import typing
    
    class A(typing.List[str]):
        pass
    

    这向您的类型检查器表明A 类的元素应该是str 类型。在运行时,这与创建list 的子类相同。 PEP 484 指定打字系统的行为方式。特别是,this section of the PEP 中的示例与您的要求类似,但使用 typing.Dict 而不是 typing.List

    在 Python 3.9+ 中,您可以use the built-in type instead of importing from typing。类变为:

    class A(list[str]):
        pass
    

    【讨论】:

      猜你喜欢
      • 2023-03-06
      • 2014-11-27
      • 1970-01-01
      • 1970-01-01
      • 2022-10-14
      • 2015-12-12
      • 2010-11-20
      • 2012-06-09
      • 1970-01-01
      相关资源
      最近更新 更多