简体   繁体   English

如何只键入协议方法的第一个位置参数并让其他人取消类型?

[英]How to type only the first positional parameter of a Protocol method and let the others be untyped?

Problem问题

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 具有除第一个参数之外的任何类型化/非类型化参数。

Workaround解决方法

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之前设置的实现方法不太清楚。

Note笔记

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.这是一组围绕具有逆变类型的通用协议的实现(绑定为浮点数,因为它是数字塔的顶部),这将允许两个xy 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

  1. 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: ...
  2. 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.

相关问题 第一个 Python argparse 位置参数只能是 str 类型吗? - Can first Python argparse positional argument only be of type str? 如何在python的用户定义函数中实现“仅位置参数”? - How to implement "positional-only parameter" in a user defined function in python? 更改其他参数时如何不更改第一个默认功能参数 - How not to change the first default function parameter when changing the others 只检测到第一个字母是假的,其他的都没有,怎么都能看出来? - Only first letter detecting as false, not others, how can all be looked at? 如何仅在满足第一个条件时才评估第二个条件? - How let second condition be evaluated only when first one is met? Google Colaboratory 中的位置参数语法“/” - Positional Only Parameter Syntax '/' in Google Colaboratory 仅更改列表中第一个元素的方法(列表作为参数传递) - Method changing first element in a list only (List passed as a parameter) 如何将类型提示添加到 Python 中的仅位置参数和仅关键字参数? - How to add Type Hints to positional-only and keyword-only parameters in Python? python只激活第一个elif,忽略其他 - python only activating first elif, ignoring others Python unittest 仅运行第一个测试而不运行其他测试 - Python unittest only running the first test and not the others
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM