Python类型提示,可索引对象

时间:2018-01-16 14:47:18

标签: python type-hinting

我的函数需要接受一个对象,从中可以通过索引提取数据,即。 __getitem__或具有已定义class IndexableContainer(Generic[int, ReturnType]): def __getitem__(self, key: int) -> ReturnType: ... 方法的实例。

我可以使用哪种类型来提示这个论点?

更新: 据我所知目前没有这种类型,我试着自己制作一个:

  File "indexable_container.py", line 22, in IndexableContainer
    class IndexableContainer(Generic[int, ReturnType]):
  File ".../lib/python3.6/typing.py", line 682, in inner
    return func(*args, **kwds)
  File ".../lib/python3.6/typing.py", line 1112, in __getitem__
    "Parameters to Generic[...] must all be type variables")
TypeError: Parameters to Generic[...] must all be type variables

但是我收到以下错误:

from django.db.models import fields
from django.utils.six import string_types
import recurrence
from recurrence import forms
from recurrence.compat import Creator

try:
    from south.modelsinspector import add_introspection_rules
add_introspection_rules([], [
    "^recurrence\.fields\.RecurrenceField",
])
except ImportError:
pass


# Do not use SubfieldBase meta class because is removed in Django 1.10

class RecurrenceField(fields.Field):
"""Field that stores a `recurrence.base.Recurrence` to the database."""

def __init__(self, include_dtstart=True, **kwargs):
    self.include_dtstart = include_dtstart
    super(RecurrenceField, self).__init__(**kwargs)

def get_internal_type(self):
    return 'TextField'

def to_python(self, value):
    if value is None or isinstance(value, recurrence.Recurrence):
        return value
    value = super(RecurrenceField, self).to_python(value) or u''
    return recurrence.deserialize(value, self.include_dtstart)

def from_db_value(self, value, *args, **kwargs):
    return self.to_python(value)

def get_prep_value(self, value):
    if not isinstance(value, string_types):
        value = recurrence.serialize(value)
    return value

def contribute_to_class(self, cls, *args, **kwargs):
    super(RecurrenceField, self).contribute_to_class(cls, *args, **kwargs)
    setattr(cls, self.name, Creator(self))

def value_to_string(self, obj):
    return self.get_prep_value(self._get_val_from_obj(obj))

def formfield(self, **kwargs):
    defaults = {
        'form_class': forms.RecurrenceField,
        'widget': forms.RecurrenceWidget,
    }
    defaults.update(kwargs)
    return super(RecurrenceField, self).formfield(**defaults)

我该怎么做?

3 个答案:

答案 0 :(得分:2)

有几种不同的方法可以做到这一点。

如果您只使用自定义类(可以编写)作为可索引容器,那么您需要做的就是调整代码并删除' int'类型参数:

class IndexableContainer(Generic[ReturnType]):
    def __getitem__(self, key: int) -> ReturnType:
        ...

class MyCustomContainer(IndexableContainer[ReturnType]):
    def __getitem__(self, key: int) -> ReturnType:
        # Implementation here

def requires_indexable_container(container: IndexableContainer[ReturnType]) -> ReturnType:
    # Code using container here

当然,问题是,如果你想将一个普通的旧列表传入函数,那么你就无法这样做,因为列表不会对你的自定义类型进行子类化。

我们可以通过巧妙地使用@overload装饰器和联合来特殊情况下输入某些输入,但这是第二种,尽管是实验性的,这种方式称为Protocols

协议基本上让你表达" duck typing"以一种理智的方式使用类型提示:基本思想是我们可以调整IndexableContainer成为一个协议。现在,任何使用适当签名实现__getitem__方法的对象都被计为有效的IndexableContainer,无论它们是否为该类型的子类。

唯一需要注意的是,协议目前是实验性的,并且(afaik)仅由mypy支持。计划是最终为一般的Python生态系统添加协议 - 请参阅PEP 544了解具体提案 - 但我没有跟踪讨论/不知道该状态是什么是

在任何情况下,要使用协议,请使用pip安装typing_extensions模块。然后,您可以执行以下操作:

from typing_extensions import Protocol

# ...snip...


class IndexableContainer(Protocol, Generic[ReturnType]):
    def __getitem__(self, key: int) -> ReturnType:
        ...

def requires_indexable_container_of_str(container: IndexableContainer[str]) -> None:
    print(container[0] + "a")

a = ["a", "b", "c"]
b = {0: "foo", 2: "bar"}
c = "abc"
d = [1, 2, 3]

# Type-checks
requires_indexable_container_of_str(a)
requires_indexable_container_of_str(b)
requires_indexable_container_of_str(c)

# Doesn't type-check
requires_indexable_container_of_str(d)

答案 1 :(得分:1)

This answer to a related question 建议 typing.Sequence。 此类型同时支持 __getitem____len__

鉴于目前已弃用,我认为最好使用 collections.abc.Sequence

正如作者后来在评论中提到的,他/她实际上也需要一些带有 __delitem__ 的东西,在这种情况下 collections.abc.MutableSequence 可能是最合适的(@@尤瓦尔在评论中)。它支持所有 __getitem____setitem____delitem____len__insert

最终类型的示例用法(改编自 the reference answer):

from collections.abc import MutableSequence

def foo(bar: MutableSequence[Any]):
    # bar is a mutable sequence of any objects

答案 2 :(得分:0)

我们最接近的似乎是:

Mapping[int, Any]

虽然它不是我想要的,但它足够接近。