typing icon indicating copy to clipboard operation
typing copied to clipboard

Add typing.Reader and typing.Writer

Open ilevkivskyi opened this issue 7 years ago • 10 comments

I recently stumbled at a TODO in typeshed that proposes to split IO[AnyStr] into smaller pieces. I think it is a good idea and propose to add two protocols:

class Reader(Protocol[AnyStr]):
    def read(self, n: int = ...) -> AnyStr: ...
    def readlines(self) -> List[AnyStr]: ...
    def close(self) -> None: ...

class Writer(Protocol[AnyStr]):
    def write(self, s: AnyStr) -> int: ...
    def writelines(self, lines: List[AnyStr]) -> None: ...
    def close(self) -> None: ...

Then IO[AnyStr] can subclass both, plus add some less often used methods (also IO should stay nominal). What do you think?

ilevkivskyi avatar Jun 16 '18 23:06 ilevkivskyi

I like this idea. Using the current IO classes in type annotations is a bit problematic because many types are kind of like files, but don't fulfill the entire IO interface (typing.IO has 23 methods and properties). If we add Reader and Writer, many argument type annotations could be changed to just one of these two.

JelleZijlstra avatar Jun 17 '18 23:06 JelleZijlstra

Of course many duck type providers for files don't even support close() or readlines(), so even the proposed Reader protocol may be too wide. (Those libraries can of course define their own Readable protocol.)

I think that close() should not be a part of these protocols -- typically closing a stream is up to the owner, i.e. the party that created (opened) it.

Also you've left out readline() -- that seems a mistake, it's probably the most commonly called I/O method.

Finally this doesn't help much for return types -- we often also want to describe that something returns a readable stream (or a writable one) but the interface promised by many APIs is much wider than the minimal protocols proposed here. This is one of the reasons I punted on this when originally designing the IO API -- the other reason was of course that we didn't have protocols then.

(There are other things one might want to express in the type system too, esp. whether seek() and tell() should work.)

gvanrossum avatar Jun 18 '18 01:06 gvanrossum

Maybe a good way to go about this is to start replacing argument types in typeshed with custom protocols in the respective modules (marked private so they do not get reused outside the module) to get a feeling what is required in practice.

srittau avatar Jun 18 '18 05:06 srittau

Maybe a good way to go about this is to start replacing argument types in typeshed with custom protocols in the respective modules (marked private so they do not get reused outside the module) to get a feeling what is required in practice.

I like this idea. If we don't do this, we risk implementing something that won't be that useful and will mostly confuse users.

JukkaL avatar Jun 18 '18 08:06 JukkaL

As seen above I submitted a first pull request implementing my last suggestion. Maybe it would be a good idea to mark those somehow, for example with a link to this issue, to be able to grep them easily later?

srittau avatar Jun 28 '18 13:06 srittau

I think just adding a link to this issue would be enough.

ilevkivskyi avatar Jun 28 '18 13:06 ilevkivskyi

@ilevkivskyi You mean in the commit message like above?

srittau avatar Jun 28 '18 13:06 srittau

You mean in the commit message like above?

I think adding the link in PR description may be better. GitHub only uses the commit message for PR description if there is a single commit in your PR, and as you can see the links to PRs are bigger than to commits.

ilevkivskyi avatar Jun 28 '18 13:06 ilevkivskyi

I think that close() should not be a part of these protocols -- typically, closing a stream is up to the owner, i.e., the party that created (opened) it.

This seems like an area where Python could look at Golang's interfaces. In the case of IO, they have (concerning this)

  • io.Reader
  • io.ReadCloser
  • io.ReadWriter
  • io.ReadWriteCloser
  • ....

This allows the end-user to operate with the specificity needed for the problem space at the cost of a more complex interface system. Personally, I've stayed away from Python's IO as it's never been clear to me what the intended use is from the docs.

References

  • https://golang.org/pkg/io/
  • https://docs.python.org/3/library/typing.html#typing.IO

kkirsche avatar May 31 '21 12:05 kkirsche

We have a good starting point for useful protocols already in the _typeshed module. These are based on the actual need of the standard library. I see the future in easy composing of protocols rather than adding various protocol with different permutations of methods. For example using intersection types (#213): SupportsRead[str] & SupportsClose.

srittau avatar May 31 '21 14:05 srittau