最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

python - What is the correct way to please the typechecker for a '(bytes | str) -> str' function? - Stack

programmeradmin3浏览0评论

I have the following code:

def from_utf8(string: bytes | str) -> str:
    if isinstance(string, bytes):
        return string.decode("utf-8")
    else:
        return string  # <- type warning on this line

pylance gives me a type warning on the return string line:

Type "bytearray | memoryview[_I@memoryview] | str" is not assignable to return type "str"
  Type "bytearray | memoryview[_I@memoryview] | str" is not assignable to type "str"
    "bytearray" is not assignable to "str"

My understanding is:
the type annotation x: bytes is actually an alias for "runtime types" x: bytes | bytearray | memoryview[_I@memoryview], but isinstance(x, bytes) only checks for bytes, not the two others.

I tried checking for types the other way around:

def from_utf8(string: bytes | str) -> str:
    if isinstance(string, str):
        return string
    else:
        return string.decode("utf-8")  # <- no attribute 'decode' for 'memoryview'

The error now becomes:

Cannot access attribute "decode" for class "memoryview[_I@memoryview]"
  Attribute "decode" is unknown

For context:

  • my project uses python 3.11
  • I see these warnings in vscode, using pylance version 2025.2.1 and python (ms-python.python) extension version 2025.0.0

Do I have a convenient way to write a version of from_utf8(string) that passes the type checker ?

also: is my assumption correct, and is it documented somewhere ?

I have the following code:

def from_utf8(string: bytes | str) -> str:
    if isinstance(string, bytes):
        return string.decode("utf-8")
    else:
        return string  # <- type warning on this line

pylance gives me a type warning on the return string line:

Type "bytearray | memoryview[_I@memoryview] | str" is not assignable to return type "str"
  Type "bytearray | memoryview[_I@memoryview] | str" is not assignable to type "str"
    "bytearray" is not assignable to "str"

My understanding is:
the type annotation x: bytes is actually an alias for "runtime types" x: bytes | bytearray | memoryview[_I@memoryview], but isinstance(x, bytes) only checks for bytes, not the two others.

I tried checking for types the other way around:

def from_utf8(string: bytes | str) -> str:
    if isinstance(string, str):
        return string
    else:
        return string.decode("utf-8")  # <- no attribute 'decode' for 'memoryview'

The error now becomes:

Cannot access attribute "decode" for class "memoryview[_I@memoryview]"
  Attribute "decode" is unknown

For context:

  • my project uses python 3.11
  • I see these warnings in vscode, using pylance version 2025.2.1 and python (ms-python.python) extension version 2025.0.0

Do I have a convenient way to write a version of from_utf8(string) that passes the type checker ?

also: is my assumption correct, and is it documented somewhere ?

Share Improve this question edited Feb 14 at 7:47 LeGEC asked Feb 14 at 5:59 LeGECLeGEC 52k5 gold badges66 silver badges125 bronze badges 5
  • 2 PEP 688 (accepted; implemented in Python 3.12) documents the thing you're assuming. The solution is to switch your isinstance check to str first (if isinstance(string, str): return string). – dROOOze Commented Feb 14 at 6:06
  • @dROOOze thanks for the pointer. The remaining issue with "the other way around" is that now, the typechecker tells me that memoryview doesn't have a .decode() method (updated my question with that information) – LeGEC Commented Feb 14 at 6:15
  • 1 Oh, pyright has a option called disableBytesTypePromotions - since you're using pylance (powered by pyright) you should be able to configure this to be true, in which case you shouldn't get the errors anymore. – dROOOze Commented Feb 14 at 6:18
  • thanks, that's a way to fix my issue :) post it as an answer and I will accept it. The PEP 688 you are pointing to applies to python 3.12, correct ? should I be worried that a python 3.11 function (from another library) that returns a bytes could actually sneak a memoryview as a value ? – LeGEC Commented Feb 14 at 6:28
  • Yes, that's a possibility if the other library's function is annotated with a return type of -> bytes, and the type-checker and options they were developing under still treated memoryview as a subtype of bytes. You're fine if the 3rd-party function isn't annotated with a return type (pyright will try to infer the return type in that case). – dROOOze Commented Feb 14 at 6:31
Add a comment  | 

1 Answer 1

Reset to default 3

Before Python 3.12, bytes was specified to behave as an alias of builtins.bytes | builtins.bytearray | builtins.memoryview. From the Python 3.10 docs (emphasis mine):

class typing.ByteString(Sequence[int])

A generic version of collections.abc.ByteString.

This type represents the types bytes, bytearray, and memoryview of byte sequences.

As a shorthand for this type, bytes can be used to annotate arguments of any of the types mentioned above.

The static typing error you're seeing is a consequence of this behaviour. This behaviour is now removed in Python 3.12 with the introduction of PEP 688.

pyright 1.1.329 (released over a year ago) has since disabled this static typing behaviour by default under strict mode. If you don't want to use strict mode but still want to disable this behaviour, set disableBytesTypePromotions to true.


As pointed out in the comments, a typed third party library may have been developed under this behaviour, in which case you should watch out when referring to variables or return values of functions from this library. As an example, without the --strict-bytes option, mypy will pass the following in type-checking (see mypy Playground):

def f() -> bytes:
    return memoryview(b"")
发布评论

评论列表(0)

  1. 暂无评论