[英]How to type only the first positional parameter of a Protocol method and let the others be untyped?
How to only type the first positional parameter of a Protocol method and let the others be untyped?如何只键入协议方法的第一个位置参数并让其他人取消类型?
Example, having a protocol named MyProtocol
that has a method named my_method
that requires only the first positional parameter to be an int, while letting the rest be untyped.例如,有一个名为
MyProtocol
的协议,它有一个名为my_method
的方法,只需要第一个位置参数是一个 int,同时让 rest 是无类型的。 the following class would implement it correctly without error:以下 class 将正确无误地实现它:
class Imp1(MyProtocol):
def my_method(self, first_param: int, x: float, y: float) -> int:
return int(first_param - x + y)
However the following implementation wouldn't implement it correctly, since the first parameter is a float:但是,以下实现不会正确实现它,因为第一个参数是浮点数:
class Imp2(MyProtocol):
def my_method(self, x: float, y: float) -> int: # Error, method must have a int parameter as a first argument after self
return int(x+y)
I imagined that I would be able to do that with *args
, and **kwargs
combined with Protocol
like so:我想象我可以用
*args
和**kwargs
结合Protocol
来做到这一点,如下所示:
from typing import Protocol, Any
class MyProtocol(Protocol):
def my_method(self, first_param: int, /, *args: Any, **kwargs: Any) -> int:
...
But (in mypy) this makes both Imp1 and Imp2 fail, because it forces the method contract to really have a *args
, **kwargs
like so:但是(在 mypy 中)这使得 Imp1 和 Imp2 都失败了,因为它强制方法契约真的有一个
*args
, **kwargs
像这样:
class Imp3(MyProtocol):
def my_method(self, first_param: int, /, *args: Any, **kwargs: Any) -> int:
return first_param
But this does not solves what I am trying to achieve, that is make the implementation class have any typed/untyped parameters except for the first parameter.但这并没有解决我想要实现的目标,即使实现 class 具有除第一个参数之外的任何类型化/非类型化参数。
I manged to circumvent the issue by using an abstract class with a setter set_first_param
, like so:我设法通过使用带有 setter
set_first_param
的抽象 class 来规避这个问题,如下所示:
from abc import ABC, abstractmethod
from typing import Any
class MyAbstractClass(ABC):
_first_param: int
def set_first_param(self, first_param: int):
self._first_param = first_param
@abstractmethod
def my_method(self, *args: Any, **kwargs: Any) -> int:
...
class AbcImp1(MyAbstractClass):
def my_method(self, x: float, y: float) -> int:
return int(self._first_param + x - y) # now i can access the first_parameter with self._first_param
But this totally changes the initial API that I am trying to achieve, and in my opinion makes less clear to the implementation method that this parameter will be set before calling my_method
.但这完全改变了我试图实现的初始 API,并且在我看来,这个参数将在调用
my_method
之前设置的实现方法不太清楚。
This example was tested using python version 3.9.13
and mypy version 0.991
.此示例使用 python 版本
3.9.13
和 mypy 版本0.991
进行了测试。
If your MyProtocol
can accept any number of arguments, you cannot have a subtype (or implementation) which accepts a set number, this breaks the Liskov substitution principle as the subtype only accepts a limited set of cases accepted by the supertype.如果您的
MyProtocol
可以接受任意数量的 arguments,则您不能拥有接受设定数字的子类型(或实现),这违反了Liskov 替换原则,因为子类型仅接受超类型接受的有限情况集。
Then, if you keep on inheriting from Protocol
, you keep on making protocols, protocols are different from ABC
s, they use structural subtyping (not nominal subtyping), meaning that as long as an object implements all the methods/properties of a protocol it is an instance of that protocol (see PEP 544 for more details).然后,如果你继续继承
Protocol
,你就会继续制定协议,协议与ABC
不同,它们使用结构子类型(而不是名义子类型),这意味着只要 object 实现协议的所有方法/属性它是该协议的一个实例(有关更多详细信息,请参阅PEP 544 )。
Without more detail on the implementations you'd want to use, @blhsing's solution is probably the most open because it does not type the Callable's call signature.如果没有关于您想要使用的实现的更多详细信息,@blhsing 的解决方案可能是最开放的,因为它不键入 Callable 的调用签名。
Here is a set of implementations around a generic protocol with contravariant types (bound to float as it is the top of the numeric tower ), which would allow any numeric type for the two x
and y
arguments.这是一组围绕具有逆变类型的通用协议的实现(绑定为浮点数,因为它是数字塔的顶部),这将允许两个
x
和y
arguments 的任何数字类型。
from typing import Any, Generic, Protocol, TypeVar
T = TypeVar("T", contravariant=True, bound=float)
U = TypeVar("U", contravariant=True, bound=float)
class MyProtocol(Protocol[T, U]):
def my_method(self, first_param: int, x: T, y: U) -> int:
...
class ImplementMyProtocol1(Generic[T, U]):
"""Generic implementation, needs typing"""
def my_method(self, first_param: int, x: T, y: U) -> int:
return int(first_param - x + y)
class ImplementMyProtocol2:
"""Float implementation, and ignores first argument"""
def my_method(self, _: int, x: float, y: float) -> int:
return int(x + y)
class ImplementMyProtocol3:
"""Another float implementation, with and extension"""
def my_method(self, first_param: int, x: float, y: float, *args: float) -> int:
return int(first_param - x + y + sum(args))
def use_MyProtocol(inst: MyProtocol[T, U], n: int, x: T, y: U) -> int:
return inst.my_method(n, x, y)
use_MyProtocol(ImplementMyProtocol1[float, float](), 1, 2.0, 3.0) # OK MyProtocol[float, float]
use_MyProtocol(ImplementMyProtocol1[int, int](), 1, 2, 3) # OK MyProtocol[int, int]
use_MyProtocol(ImplementMyProtocol2(), 1, 2.0, 3.0) # OK MyProtocol[float, float]
use_MyProtocol(ImplementMyProtocol3(), 1, 2.0, 3.0) # OK MyProtocol[float, float]
One reasonable workaround would be to make the method take just the typed arguments, and leave the untyped arguments to a callable that the method returns.一种合理的解决方法是使该方法仅采用类型化的 arguments,并将未类型化的 arguments 留给该方法返回的可调用对象。 Since you can declare the return type of a callable without specifying the call signature by using an ellipsis, it solves your problem of leaving those additional arguments untyped:
由于您可以在不使用省略号指定调用签名的情况下声明可调用对象的返回类型,因此它解决了保留那些额外的 arguments 未类型化的问题:
from typing import Protocol, Callable
class MyProtocol(Protocol):
def my_method(self, first_param: int) -> Callable[..., int]:
...
class Imp1(MyProtocol):
def my_method(self, first_param: int) -> Callable[..., int]:
def _my_method(x: float, y: float) -> int:
return int(first_param - x + y)
return _my_method
print(Imp1().my_method(5)(1.5, 2.5)) # outputs 6
Demo of the code passing mypy:通过mypy的代码演示:
https://mypy-play.net/?mypy=latest&python=3.12&gist=677569f73f6fc3bc6e44858ef37e9faf https://mypy-play.net/?mypy=latest&python=3.12&gist=677569f73f6fc3bc6e44858ef37e9faf
Signature of method 'Imp1.my_method()' does not match signature of the base method in class 'MyProtocol'方法 'Imp1.my_method()' 的签名与 class 'MyProtocol' 中基本方法的签名不匹配
must be I suppose我想一定是
class Imp1(MyProtocol): def my_method(self, first_param: int, *args: Any, **kwargs: Any) -> int: ...
Yours Imp2 the same as in Imp1 but does not even have first named parameter.你的 Imp2 与 Imp1 中的相同,但甚至没有第一个命名参数。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.