如何根据参数重载类成员类型?

问题描述 投票:0回答:1

我最近了解到我可以使用

Literal[True]
Literal[False]
参数重载返回类型。我正在实现自己的
subprocess.Popen
接口,但无法将
self.stdin
返回类型重载为
IO[bytes]
IO[str]
,具体取决于构造函数中
text
的值。我正在使用 Pyright 进行静态类型检查。

我尝试过以下方法:

from typing import IO, Literal, Optional, overload


class MyPopen:
    @overload
    def __init__(self, text: Literal[False] = False):
        self.stdin: Optional[IO[bytes]]

    @overload
    def __init__(self, text: Literal[True] = True):
        self.stdin: Optional[IO[str]]

    def __init__(self, text: bool = False):
        self.stdin = None


pp = MyPopen(text=True)
assert pp.stdin
pp.stdin.write("text")    # should be ok
pp.stdin.write(b"text")   # should error
pp = MyPopen(text=False)
assert pp.stdin
pp.stdin.write("text")    # should error
pp.stdin.write(b"text")   # should be ok

但是,它假定

pp.stdin
IO[str]
两次,并且声明 stdin 被隐藏:

$ pyright /dev/stdin <1.py
/dev/stdin
  /dev/stdin:7:14 - error: Declaration "stdin" is obscured by a declaration of the same name (reportRedeclaration)
  /dev/stdin:20:1 - error: No overloads for "write" match the provided arguments (reportCallIssue)
  /dev/stdin:20:16 - error: Argument of type "Literal[b"text"]" cannot be assigned to parameter "__s" of type "str" in function "write"
    "Literal[b"text"]" is incompatible with "str" (reportArgumentType)
  /dev/stdin:24:1 - error: No overloads for "write" match the provided arguments (reportCallIssue)
  /dev/stdin:24:16 - error: Argument of type "Literal[b"text"]" cannot be assigned to parameter "__s" of type "str" in function "write"
    "Literal[b"text"]" is incompatible with "str" (reportArgumentType)
5 errors, 0 warnings, 0 informations 

如何根据参数的值重载类实例成员类型?

注意它如何与 subprocess.Popen 一起使用:

import subprocess
pp = subprocess.Popen("", text=True)
assert pp.stdout
pp.stdout.write("text")   # ok
pp.stdout.write(b"text")  # error
pp = subprocess.Popen("", text=False)
assert pp.stdout
pp.stdout.write("text")   # error
pp.stdout.write(b"text")  # ok

我尝试阅读子进程源代码https://github.com/python/cpython/blob/main/Lib/subprocess.py#L1015,但我不知道类型注释存储在哪里。

python python-3.x subprocess typing pyright
1个回答
0
投票

我认为这不是

@overload
的有效使用。它用于为函数指定更精确的签名,而不是有条件地设置属性的类型。

但是,您可以创建两个空子类,一个用于

str
,一个用于
bytes
,然后让
MyPopen.__new__()
返回其中任何一个,并在
that
上使用 @overload。除了充当预表之外,他们不需要做任何事情;逻辑仍将在
MyPopen
中定义。

(游乐场链接:mypyPyright

# Or T = TypeVar('T', str, bytes); class MyPopen(Generic[T]): ...
class MyPopen[T: (str, bytes)]:

  stdin: IO[T] | None

  # Now you can use @overload!

  @overload
  def __new__(cls, text: Literal[False]) -> _BytesPopen:
    ...

  @overload
  def __new__(cls, text: Literal[True]) -> _StrPopen:
    ...

  @overload
  def __new__(cls) -> _BytesPopen:
    ...

  def __new__(cls, text: bool = False) -> _BytesPopen | _StrPopen:
    if text is True:
      subclass = _StrPopen
    else:
      subclass = _BytesPopen

    # Use `object.__new__()` or `type: ignore` to pass strict mode
    instance = super().__new__(subclass)
    instance.stdin = None

    return instance

  def write(self, output: T) -> None:
    ...
class _StrPopen(MyPopen[str]):
  pass

class _BytesPopen(MyPopen[bytes]):
  pass

结果将是:

pp = MyPopen()
assert pp.stdin
pp.stdin.write("text")    # error: "Literal['text']" is incompatible with "bytes"
pp.stdin.write(b"text")   # fine
pp = MyPopen(text=False)
assert pp.stdin
pp.stdin.write("text")    # error: "Literal['text']" is incompatible with "bytes"
pp.stdin.write(b"text")   # fine
pp = MyPopen(text=True)
assert pp.stdin
pp.stdin.write("text")    # fine
pp.stdin.write(b"text")   # error: "Literal[b"text"]" is incompatible with "str"
© www.soinside.com 2019 - 2024. All rights reserved.