我遇到了一个可以通过交叉类型轻松解决的问题(目前正在讨论但尚未实施),并且想知道最干净的解决方法是什么。
我当前的设置大致对应于以下动物的 ABC 层次结构。有许多动物“特征”(CanFly、CanSwim等)被定义为抽象子类(尽管它们也可以被定义为 mixin)。
from abc import ABC, abstractmethod
class Animal(ABC):
@abstractmethod
def name(self) -> str: ...
class CanFly(Animal):
@abstractmethod
def fly(self) -> None: ...
class CanSwim(Animal):
@abstractmethod
def swim(self) -> None: ...
Run Code Online (Sandbox Code Playgroud)
以此我定义了特定的动物类别,包括抽象的和具体的:
class Bird(CanFly):
def fly(self) -> None:
print("flap wings")
class Penguin(Bird, CanSwim):
def name(self) -> str:
return "penguin"
def swim(self) -> None:
print("paddle flippers")
Run Code Online (Sandbox Code Playgroud)
我还定义了一个通用类来抚摸特定类型的动物:
from typing import Generic, TypeVar
T = TypeVar("T", bound=Animal, contravariant=True)
class Petter(Generic[T], ABC):
@abstractmethod …Run Code Online (Sandbox Code Playgroud) 我正在 Python 3.8+ Django/Rest-Framework 环境中工作,在新代码中强制执行类型,但建立在许多无类型的遗留代码和数据之上。我们广泛使用 TypedDicts 来确保我们生成的数据以正确的数据类型传递到 TypeScript 前端。
MyPy/PyCharm/等。在检查我们的新代码是否输出符合要求的数据方面做得很好,但我们想测试我们的许多 RestSerializers/ModelSerializers 的输出是否符合 TypeDict。如果我有一个序列化器并输入如下字典:
class PersonSerializer(ModelSerializer):
class Meta:
model = Person
fields = ['first', 'last']
class PersonData(TypedDict):
first: str
last: str
email: str
Run Code Online (Sandbox Code Playgroud)
然后运行如下代码:
person_dict: PersonData = PersonSerializer(Person.objects.first()).data
Run Code Online (Sandbox Code Playgroud)
静态类型检查器无法发现person_dict缺少所需email密钥,因为(根据 PEP-589 的设计)它只是一个普通的dict. 但我可以写这样的东西:
annotations = PersonData.__annotations__
for k in annotations:
assert k in person_dict # or something more complex.
assert isinstance(person_dict[k], annotations[k])
Run Code Online (Sandbox Code Playgroud)
它会发现email序列化器的数据丢失了。在这种情况下,这很好,我没有引入任何更改from __future__ import annotations(不确定这是否会破坏它),并且我的所有类型注释都是裸类型。但如果PersonData定义如下:
class PersonData(TypedDict):
email: Optional[str] …Run Code Online (Sandbox Code Playgroud) 我在几个项目中使用Typeguard在 Python 运行时进行类型检查。它运作得很好。
我遇到过一种情况,函数参数的类型是typing.Union由一些动态收集的数据类型组成的。例如
def find_datatypes():
# some stuff ...
return (str, int) # dynamically generated list / tuple
datatypes = find_datatypes()
Run Code Online (Sandbox Code Playgroud)
现在我想生成一个typing.Unionfromdatatypes以便最终在函数中使用。我期望解包语法能够工作:
my_union = typing.Union[*datatypes]
@typeguard.typechecked
def some_function(param: my_union):
pass
Run Code Online (Sandbox Code Playgroud)
然而,它并没有:
my_union = typing.Union[*datatypes]
^
SyntaxError: invalid syntax
Run Code Online (Sandbox Code Playgroud)
我怎样才能实现我想要的目标?
我无法理解Genericand的用法TypeVar以及它们之间的关系。
https://docs.python.org/3/library/typing.html#building-generic-types
文档有这个例子:
class Mapping(Generic[KT, VT]):
def __getitem__(self, key: KT) -> VT:
...
# Etc.
Run Code Online (Sandbox Code Playgroud)
X = TypeVar('X')
Y = TypeVar('Y')
def lookup_name(mapping: Mapping[X, Y], key: X, default: Y) -> Y:
try:
return mapping[key]
except KeyError:
return default
Run Code Online (Sandbox Code Playgroud)
类型变量的存在主要是为了静态类型检查器的利益。它们用作泛型类型以及泛型函数定义的参数。
为什么我不能简单地使用Mapping某些现有类型,例如int, 而不是创建Xand Y?
从PEP 563来看,似乎应该有轻微的性能提升,因为它解决了
类型提示在模块导入时执行,这在计算上不是免费的。
那么...我是否可以/应该将其包含from __future__ import annotations在包中的每个文件中,或者是否有任何原因应将其从某些文件中排除?
我在下面有这个功能;
def time_in_range(start, end, x):
"""Return true if x is in the range [start, end]"""
if start <= end:
return start <= x <= end
else:
return start <= x or x <= end
Run Code Online (Sandbox Code Playgroud)
函数参数均为日期时间类型。我想向函数添加输入提示。这就是我所做的;
def time_in_range(start: datetime, end: datetime, x: datetime) -> bool:
"""Return true if x is in the range [start, end]"""
if start <= end:
return start <= x <= end
else:
return start <= x or x <= end
Run Code Online (Sandbox Code Playgroud)
我收到错误NameError: name 'datetime' is not defined …
在新发布的Python 3.8中,有一个新类型的注释typing.TypedDict。其文档中提到
可以通过
Point2D.__annotations__和访问用于自省的类型信息Point2D.__total__。[....]
尽管__annotations__是众所周知的,但已在PEP 3107中进行了介绍,但我在上找不到任何信息__total__。谁能解释它的含义,并在可能的情况下链接到权威资料来源?
我有类似的东西:
from typing import Type
class Foo:
pass
def make_a_foobar_class(foo_class: Type[Foo]) -> Type[Foo]:
class FooBar(foo_class):
# this.py:10: error: Variable "foo_class" is not valid as a type
# this.py:10: error: Invalid base class "foo_class"
pass
return FooBar
print(make_a_foobar_class(Foo)())
Run Code Online (Sandbox Code Playgroud)
运行会mypy在该行抛出这两个错误(作为注释添加 ^)class FooBar(foo_class):
该代码似乎工作得很好:
$ python this.py
<__main__.make_a_foobar_class.<locals>.FooBar object at 0x10a422be0>
Run Code Online (Sandbox Code Playgroud)
我究竟做错了什么?
基本上是这个问题(尚未得到解答)的精炼版本。
我想声明的是,变量应该只采用TypedDict.
目前我正在定义一个单独的Literal类型来表示键,例如:
from typing import Literal, TypedDict
class MyTD(TypedDict):
a: int
b: int
mytd = MyTD(a=1, b=2)
key = "a"
mytd[key] # error: TypedDict key must be a string literal; expected one of ('a', 'b')
MyTDKeyT = Literal["a", "b"]
typed_key: MyTDKeyT = "b"
mytd[typed_key] # no error
Run Code Online (Sandbox Code Playgroud)
我希望能够Literal出于想要最小化重复代码的所有常见原因替换定义。
伪代码:
key: Keys[MyTD] = "a"
mytd[key] # would be no error
not_key: Keys[MyTD] = "z" # error
Run Code Online (Sandbox Code Playgroud)
有办法实现这一点吗?
为了澄清,鉴于 mypy 可以告诉我键类型需要是“a”或“b”的文字,我希望可能有一种不太容易出错的方法来将变量注释为该类型,而不是使用并排维护两个单独的键列表,一次在定义中TypedDict,一次在Literal …
mypy v0.910 拒绝 Python 3.9 中的抽象数据类。这是最小的可重复示例:
from abc import ABC, abstractmethod
from dataclasses import dataclass
@dataclass
class Liquid(ABC):
@abstractmethod
def drip(self) -> None:
pass
Run Code Online (Sandbox Code Playgroud)
这是错误消息:
$ mypy --python-version 3.9 so.py
so.py:4: error: Only concrete class can be given where "Type[Liquid]" is expected
Found 1 error in 1 file (checked 1 source file)
Run Code Online (Sandbox Code Playgroud)
如何让此代码通过mypy?
笔记
我从mypy 问题 #5374中了解到,这是mypy中的一个错误,于 2018 年首次注意到,但仍未得到纠正。不过,我认为人们必须将mypy与抽象数据类一起使用,因此必须有一种解决方法或正确的方法来定义或注释该类。有什么推荐的?
错误消息的基础似乎是mypy假设任何类型的对象都Type可以实例化,但抽象类不能实例化。这似乎是错误,因为Type被定义为表示类对象,而不一定是具体的类对象(即可以实例化的类对象)。
添加# type: ignore到包含的行 …
python abstract-base-class mypy python-dataclasses python-typing
python-typing ×10
python ×8
mypy ×5
python-3.x ×4
type-hinting ×3
abc ×1
annotations ×1
typeddict ×1