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 |1 Answer
Reset to default 3Before 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
, andmemoryview
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"")
isinstance
check tostr
first (if isinstance(string, str): return string
). – dROOOze Commented Feb 14 at 6:06memoryview
doesn't have a.decode()
method (updated my question with that information) – LeGEC Commented Feb 14 at 6:15disableBytesTypePromotions
- since you're using pylance (powered by pyright) you should be able to configure this to betrue
, in which case you shouldn't get the errors anymore. – dROOOze Commented Feb 14 at 6:18bytes
could actually sneak amemoryview
as a value ? – LeGEC Commented Feb 14 at 6:28-> bytes
, and the type-checker and options they were developing under still treatedmemoryview
as a subtype ofbytes
. 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