- bounty将在16小时后过期**。回答此问题可获得+50的声望奖励。giuliano-oliveira正在寻找规范答案。
问题
如何只输入Protocol方法的第一个位置参数,而不输入其他参数?
例如,有一个名为MyProtocol
的协议,该协议有一个名为my_method
的方法,该方法只要求第一个位置参数为int,而其余参数不需要类型化。下面的类将正确地实现它,没有错误:
class Imp1(MyProtocol):
def my_method(self, first_param: int, x: float, y: float) -> int:
return int(first_param - x + y)
但是,由于第一个参数是浮点型,因此下面的实现无法正确实现它:
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)
我想象我可以用*args
来实现这一点,**kwargs
和Protocol
的组合如下:
from typing import Protocol, Any
class MyProtocol(Protocol):
def my_method(self, first_param: int, /, *args: Any, **kwargs: Any) -> int:
...
但是(在mypy中)这会使Imp1和Imp2都失败,因为它强制方法契约实际上具有*args
,**kwargs
,如下所示:
class Imp3(MyProtocol):
def my_method(self, first_param: int, /, *args: Any, **kwargs: Any) -> int:
return first_param
但这并没有解决我试图实现的目标,即使实现类具有除第一个参数之外的任何类型化/非类型化参数。
变通方案
我设法通过使用一个带有setter set_first_param
的抽象类来避免这个问题,如下所示:
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
但这完全改变了我试图实现的初始API,而且在我看来,实现方法不太清楚这个参数将在调用my_method
之前设置。
注解
这个例子是用python版本3.9.13
和mypy版本0.991
测试的。
2条答案
按热度按时间vngu2lb81#
一个合理的解决方法是让方法只接受类型化参数,而将非类型化参数留给方法返回的可调用对象。由于您可以使用省略号声明可调用对象的返回类型,而无需指定调用签名,因此它解决了将这些附加参数保留为非类型化参数的问题:
演示代码传递mypy:
https://mypy-play.net/?mypy=latest&python=3.12&gist=677569f73f6fc3bc6e44858ef37e9faf
eanckbw92#
如果你的
MyProtocol
可以接受任意数量的参数,你就不能有一个接受一组数量的子类型(或实现),这就破坏了Liskov substitution principle,因为子类型只接受父类型所接受的有限的一组情况。然后,如果你继续从
Protocol
继承,你继续制定协议,协议不同于ABC
,它们使用结构化子类型(而不是名义子类型),这意味着只要一个对象实现了协议的所有方法/属性,它就是该协议的示例(更多细节请参见PEP 544)。如果没有关于您想要使用的实现的更多细节,@blhsing的解决方案可能是最开放的,因为它不输入Callable的调用签名。
下面是一组围绕具有逆变类型(绑定为float,因为它是数字塔的顶部)的通用协议的实现,这将允许两个
x
和y
参数使用任何数字类型。