Skip to content

"typing.BinaryIO" missing "readinto" method #133492

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
DRayX opened this issue May 6, 2025 · 11 comments
Open

"typing.BinaryIO" missing "readinto" method #133492

DRayX opened this issue May 6, 2025 · 11 comments
Labels
stdlib Python modules in the Lib dir topic-typing type-bug An unexpected behavior, bug, or error

Comments

@DRayX
Copy link

DRayX commented May 6, 2025

Bug report

Bug description:

typeing.BinaryIO is completely missing the readinto method. All binary mode return values of open support the readinto method, and the lack of it on the BinaryIO type makes type-hinting difficult when using this method. One could try to use io.RawIOBase | io.BufferedIOBase but this looses the fact that the result of open has a name property. This has been brought up in python/typing#659 and python/typeshed#2166.

Furthmore, the write method in BinaryIO is declared to only be compatible with Union[bytes, bytearray] when it should be anything that satisfies collections.abc.Buffer.

I propose that BinaryIO should look something more like:

class BinaryIO(IO[bytes]):
    """Typed version of the return of open() in binary mode."""

    __slots__ = ()

    @abstractmethod
    def readinto(self, b: collections.abc.Buffer) -> int:
        pass

    @abstractmethod
    def write(self, s: collections.abc.Buffer) -> int:
        pass

    @abstractmethod
    def __enter__(self) -> 'BinaryIO':
        pass

CPython versions tested on:

CPython main branch

Operating systems tested on:

No response

@DRayX DRayX added the type-bug An unexpected behavior, bug, or error label May 6, 2025
@DRayX DRayX changed the title typing.BinaryIO missing readinto method "typing.BinaryIO" missing "readinto" method May 6, 2025
@DRayX
Copy link
Author

DRayX commented May 6, 2025

I think this should be fixed in typing.BinaryIO before being fixed in typeshed (python/typeshed#2166).

@DRayX
Copy link
Author

DRayX commented May 6, 2025

The first change should be in typing, and then there is a followup (similar) change to typeshed. The actual change (to both) is pretty straightforward; I suspect it's more a question of should this method be included on BinaryIO. Considering that the io.FileIO, io.BufferedReader, io.BufferedWriter, and io.BufferedRandom (the 4 binary types that are actually returned from open) all support readinto, I feel pretty strongly that it should be included.

@pavan-msys
Copy link

Hi @DRayX

Thanks for confirming

@mkaraev
Copy link
Contributor

mkaraev commented May 6, 2025

Hi, @ZeroIntensity
Is someone working on this issue? If not can I work on it?

@ZeroIntensity
Copy link
Member

I'm not an expert here, but at a glance, changing the rules in an established protocol sounds like a no-go. I'd like to hear from @JelleZijlstra or @picnixz before giving someone a green light to work on this.

@JelleZijlstra
Copy link
Member

As background on the IO classes, they're somewhat problematic in general because they don't describe real IO classes well. @srittau has been on a long-term mission to move away from IO etc. towards smaller, tailored protocols.

changing the rules in an established protocol

One of the weird things about these classes (IO, BinaryIO) is that they are not technically Protocols or ABCs. Instead, they are declared as regular classes; they have no subclasses at runtime but in typeshed we declare certain classes as subclasses anyway, so they mostly work in type annotations. This leads to unsound behavior; for example, type checkers will accept this:

import io, typing

def f(x: int | io.FileIO) -> int:
    if isinstance(x, typing.BinaryIO):
        return x.fileno()
    return x

But it also means that adding .readinto() is not terribly unsafe. I'd be fine with doing this it can be shown that the method is present on all classes in typeshed that are currently marked as inheriting from BinaryIO.

the write method in BinaryIO is declared to only be compatible with Union[bytes, bytearray] when it should be anything that satisfies collections.abc.Buffer

This is already OK in typeshed; the runtime definitions of the methods in typing.py aren't used by type checkers. I wouldn't mind a PR updating the typing.py definitions to match those in typeshed better though.

@srittau
Copy link
Contributor

srittau commented May 6, 2025

What practical problem does this solve? I would rather see type annotations fixed to use either concrete types or appropriate protocols than to encourage the use of these problematic pseudo-protocols.

@DRayX
Copy link
Author

DRayX commented May 6, 2025

It's a bit contrived, but if you have a method that takes in the result of an open call with unknown buffering and wants to call readinto, it's difficult to type this parameter. A Protocol works well if you only need the readinto method (and allows for easily using objects that only need that method), but quickly becomes cumbersome if you need a bunch of the other methods or properties. The best I could come up with is io.FileIO | io.BufferedReader, but there isn't a strong guarantee these are the only return types from open. The base return type for all binary mode open calls is BinaryIO so using this as the parameter type seems like the "correct" option, but then trying to call readinto fails type checking.

@srittau
Copy link
Contributor

srittau commented May 6, 2025

I wonder whether it would make sense to have a protocol or type alias in _typeshed that represents the common interface of all possible open() return types. That said, I'm "only" -0 on adding more methods to BinaryIO et al., so I would be ok with it if it's the most practical solution. On the other hand, adding it to Python 3.15+ (or even squeezing it in for 3.14+) means that practically it can't be used for quite some time, so a _typeshed solution might be more practical.

@DRayX
Copy link
Author

DRayX commented May 6, 2025

I agree that a typeshed solution would be much more expedient. The comment you (@srittau) made on python/typeshed#2166 was that this should be resolved in typing first. I think if the consensus is that this change to typing is OK and the method is added in 3.15 (or 3.14) that would make the change in typeshed more palatable; even though they would diverge for some period of time, they would be consistent with with Python 3.15.

I do agree though that long term moving typing.IO and it's subclasses to be protocols would make sense and give them more runtime utility. That seems like a much larger and even longer term change though. I think discussion of that could take place on the new python/typing#1994 issue. If there's no objections to adding readinto to typing.BianryIO (and then to typeshed) that seems like a much simpler and practical short- to medium- term improvement.

@srittau
Copy link
Contributor

srittau commented May 6, 2025

Please note that the change to typeshed would follow the same version constraints as those of CPython. I.e. if it's added to Python 3.14, the new method will be behind a version guard in typeshed as well.

@picnixz picnixz added the stdlib Python modules in the Lib dir label May 7, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
stdlib Python modules in the Lib dir topic-typing type-bug An unexpected behavior, bug, or error
Projects
None yet
Development

No branches or pull requests

7 participants