Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitc6dd234

Browse files
authored
gh-127647: Add typing.Reader and Writer protocols (#127648)
1 parent9c69150 commitc6dd234

File tree

9 files changed

+192
-9
lines changed

9 files changed

+192
-9
lines changed

‎Doc/library/io.rst

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1147,6 +1147,55 @@ Text I/O
11471147
It inherits from:class:`codecs.IncrementalDecoder`.
11481148

11491149

1150+
Static Typing
1151+
-------------
1152+
1153+
The following protocols can be used for annotating function and method
1154+
arguments for simple stream reading or writing operations. They are decorated
1155+
with:deco:`typing.runtime_checkable`.
1156+
1157+
..class::Reader[T]
1158+
1159+
Generic protocol for reading from a file or other input stream. ``T`` will
1160+
usually be:class:`str` or:class:`bytes`, but can be any type that is
1161+
read from the stream.
1162+
1163+
..versionadded::next
1164+
1165+
..method::read()
1166+
read(size, /)
1167+
1168+
Read data from the input stream and return it. If *size* is
1169+
specified, it should be an integer, and at most *size* items
1170+
(bytes/characters) will be read.
1171+
1172+
For example::
1173+
1174+
def read_it(reader: Reader[str]):
1175+
data = reader.read(11)
1176+
assert isinstance(data, str)
1177+
1178+
..class::Writer[T]
1179+
1180+
Generic protocol for writing to a file or other output stream. ``T`` will
1181+
usually be:class:`str` or:class:`bytes`, but can be any type that can be
1182+
written to the stream.
1183+
1184+
..versionadded::next
1185+
1186+
..method::write(data, /)
1187+
1188+
Write *data* to the output stream and return the number of items
1189+
(bytes/characters) written.
1190+
1191+
For example::
1192+
1193+
def write_binary(writer: Writer[bytes]):
1194+
writer.write(b"Hello world!\n")
1195+
1196+
See:ref:`typing-io` for other I/O related protocols and classes that can be
1197+
used for static type checking.
1198+
11501199
Performance
11511200
-----------
11521201

‎Doc/library/typing.rst

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2834,17 +2834,35 @@ with :func:`@runtime_checkable <runtime_checkable>`.
28342834
An ABC with one abstract method ``__round__``
28352835
that is covariant in its return type.
28362836

2837-
ABCs for working with IO
2838-
------------------------
2837+
.. _typing-io:
2838+
2839+
ABCs and Protocols for working with I/O
2840+
---------------------------------------
28392841

2840-
..class::IO
2841-
TextIO
2842-
BinaryIO
2842+
..class::IO[AnyStr]
2843+
TextIO[AnyStr]
2844+
BinaryIO[AnyStr]
28432845

2844-
Generictype ``IO[AnyStr]`` and its subclasses ``TextIO(IO[str])``
2846+
Genericclass ``IO[AnyStr]`` and its subclasses ``TextIO(IO[str])``
28452847
and ``BinaryIO(IO[bytes])``
28462848
represent the types of I/O streams such as returned by
2847-
:func:`open`.
2849+
:func:`open`. Please note that these classes are not protocols, and
2850+
their interface is fairly broad.
2851+
2852+
The protocols:class:`io.Reader` and:class:`io.Writer` offer a simpler
2853+
alternative for argument types, when only the ``read()`` or ``write()``
2854+
methods are accessed, respectively::
2855+
2856+
def read_and_write(reader: Reader[str], writer: Writer[bytes]):
2857+
data = reader.read()
2858+
writer.write(data.encode())
2859+
2860+
Also consider using:class:`collections.abc.Iterable` for iterating over
2861+
the lines of an input stream::
2862+
2863+
def read_config(stream: Iterable[str]):
2864+
for line in stream:
2865+
...
28482866

28492867
Functions and decorators
28502868
------------------------

‎Doc/whatsnew/3.14.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -619,6 +619,11 @@ io
619619
:exc:`BlockingIOError` if the operation cannot immediately return bytes.
620620
(Contributed by Giovanni Siragusa in:gh:`109523`.)
621621

622+
* Add protocols:class:`io.Reader` and:class:`io.Writer` as a simpler
623+
alternatives to the pseudo-protocols:class:`typing.IO`,
624+
:class:`typing.TextIO`, and:class:`typing.BinaryIO`.
625+
(Contributed by Sebastian Rittau in:gh:`127648`.)
626+
622627

623628
json
624629
----

‎Lib/_pyio.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
_setmode=None
1717

1818
importio
19-
fromioimport (__all__,SEEK_SET,SEEK_CUR,SEEK_END)# noqa: F401
19+
fromioimport (__all__,SEEK_SET,SEEK_CUR,SEEK_END,Reader,Writer)# noqa: F401
2020

2121
valid_seek_flags= {0,1,2}# Hardwired values
2222
ifhasattr(os,'SEEK_HOLE') :

‎Lib/io.py

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,14 @@
4646
"BufferedReader","BufferedWriter","BufferedRWPair",
4747
"BufferedRandom","TextIOBase","TextIOWrapper",
4848
"UnsupportedOperation","SEEK_SET","SEEK_CUR","SEEK_END",
49-
"DEFAULT_BUFFER_SIZE","text_encoding","IncrementalNewlineDecoder"]
49+
"DEFAULT_BUFFER_SIZE","text_encoding","IncrementalNewlineDecoder",
50+
"Reader","Writer"]
5051

5152

5253
import_io
5354
importabc
5455

56+
from_collections_abcimport_check_methods
5557
from_ioimport (DEFAULT_BUFFER_SIZE,BlockingIOError,UnsupportedOperation,
5658
open,open_code,FileIO,BytesIO,StringIO,BufferedReader,
5759
BufferedWriter,BufferedRWPair,BufferedRandom,
@@ -97,3 +99,55 @@ class TextIOBase(_io._TextIOBase, IOBase):
9799
pass
98100
else:
99101
RawIOBase.register(_WindowsConsoleIO)
102+
103+
#
104+
# Static Typing Support
105+
#
106+
107+
GenericAlias=type(list[int])
108+
109+
110+
classReader(metaclass=abc.ABCMeta):
111+
"""Protocol for simple I/O reader instances.
112+
113+
This protocol only supports blocking I/O.
114+
"""
115+
116+
__slots__= ()
117+
118+
@abc.abstractmethod
119+
defread(self,size=...,/):
120+
"""Read data from the input stream and return it.
121+
122+
If *size* is specified, at most *size* items (bytes/characters) will be
123+
read.
124+
"""
125+
126+
@classmethod
127+
def__subclasshook__(cls,C):
128+
ifclsisReader:
129+
return_check_methods(C,"read")
130+
returnNotImplemented
131+
132+
__class_getitem__=classmethod(GenericAlias)
133+
134+
135+
classWriter(metaclass=abc.ABCMeta):
136+
"""Protocol for simple I/O writer instances.
137+
138+
This protocol only supports blocking I/O.
139+
"""
140+
141+
__slots__= ()
142+
143+
@abc.abstractmethod
144+
defwrite(self,data,/):
145+
"""Write *data* to the output stream and return the number of items written."""
146+
147+
@classmethod
148+
def__subclasshook__(cls,C):
149+
ifclsisWriter:
150+
return_check_methods(C,"write")
151+
returnNotImplemented
152+
153+
__class_getitem__=classmethod(GenericAlias)

‎Lib/test/test_io.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4916,6 +4916,24 @@ class PySignalsTest(SignalsTest):
49164916
test_reentrant_write_text=None
49174917

49184918

4919+
classProtocolsTest(unittest.TestCase):
4920+
classMyReader:
4921+
defread(self,sz=-1):
4922+
returnb""
4923+
4924+
classMyWriter:
4925+
defwrite(self,b:bytes):
4926+
pass
4927+
4928+
deftest_reader_subclass(self):
4929+
self.assertIsSubclass(MyReader,io.Reader[bytes])
4930+
self.assertNotIsSubclass(str,io.Reader[bytes])
4931+
4932+
deftest_writer_subclass(self):
4933+
self.assertIsSubclass(MyWriter,io.Writer[bytes])
4934+
self.assertNotIsSubclass(str,io.Writer[bytes])
4935+
4936+
49194937
defload_tests(loader,tests,pattern):
49204938
tests= (CIOTest,PyIOTest,APIMismatchTest,
49214939
CBufferedReaderTest,PyBufferedReaderTest,

‎Lib/test/test_typing.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
fromfunctoolsimportlru_cache,wraps,reduce
77
importgc
88
importinspect
9+
importio
910
importitertools
1011
importoperator
1112
importos
@@ -4294,6 +4295,40 @@ def __release_buffer__(self, mv: memoryview) -> None:
42944295
self.assertNotIsSubclass(C,ReleasableBuffer)
42954296
self.assertNotIsInstance(C(),ReleasableBuffer)
42964297

4298+
deftest_io_reader_protocol_allowed(self):
4299+
@runtime_checkable
4300+
classCustomReader(io.Reader[bytes],Protocol):
4301+
defclose(self): ...
4302+
4303+
classA:pass
4304+
classB:
4305+
defread(self,sz=-1):
4306+
returnb""
4307+
defclose(self):
4308+
pass
4309+
4310+
self.assertIsSubclass(B,CustomReader)
4311+
self.assertIsInstance(B(),CustomReader)
4312+
self.assertNotIsSubclass(A,CustomReader)
4313+
self.assertNotIsInstance(A(),CustomReader)
4314+
4315+
deftest_io_writer_protocol_allowed(self):
4316+
@runtime_checkable
4317+
classCustomWriter(io.Writer[bytes],Protocol):
4318+
defclose(self): ...
4319+
4320+
classA:pass
4321+
classB:
4322+
defwrite(self,b):
4323+
pass
4324+
defclose(self):
4325+
pass
4326+
4327+
self.assertIsSubclass(B,CustomWriter)
4328+
self.assertIsInstance(B(),CustomWriter)
4329+
self.assertNotIsSubclass(A,CustomWriter)
4330+
self.assertNotIsInstance(A(),CustomWriter)
4331+
42974332
deftest_builtin_protocol_allowlist(self):
42984333
withself.assertRaises(TypeError):
42994334
classCustomProtocol(TestCase,Protocol):

‎Lib/typing.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1876,6 +1876,7 @@ def _allow_reckless_class_checks(depth=2):
18761876
'Reversible','Buffer',
18771877
],
18781878
'contextlib': ['AbstractContextManager','AbstractAsyncContextManager'],
1879+
'io': ['Reader','Writer'],
18791880
'os': ['PathLike'],
18801881
}
18811882

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Add protocols:class:`io.Reader` and:class:`io.Writer` as
2+
alternatives to:class:`typing.IO`,:class:`typing.TextIO`, and
3+
:class:`typing.BinaryIO`.

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp