| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1147,6 +1147,55 @@ Text I/O | |||
| 1147 | 1147 | It inherits from :class:`codecs.IncrementalDecoder`. | |
| 1148 | 1148 | ||
| 1149 | 1149 | ||
| 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 | + | ||
| 1150 | 1199 | Performance | |
| 1151 | 1200 | ----------- | |
| 1152 | 1201 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2834,17 +2834,35 @@ with :func:`@runtime_checkable <runtime_checkable>`. | |||
| 2834 | 2834 | An ABC with one abstract method ``__round__`` | |
| 2835 | 2835 | that is covariant in its return type. | |
| 2836 | 2836 | ||
| 2837 | - ABCs for working with IO | ||
| 2838 | - ------------------------ | ||
| 2837 | + .. _typing-io: | ||
| 2838 | + | ||
| 2839 | + ABCs and Protocols for working with I/O | ||
| 2840 | + --------------------------------------- | ||
| 2839 | 2841 | ||
| 2840 | - .. class:: IO | ||
| 2841 | - TextIO | ||
| 2842 | - BinaryIO | ||
| 2842 | + .. class:: IO[AnyStr] | ||
| 2843 | + TextIO[AnyStr] | ||
| 2844 | + BinaryIO[AnyStr] | ||
| 2843 | 2845 | ||
| 2844 | - Generic type ``IO[AnyStr]`` and its subclasses ``TextIO(IO[str])`` | ||
| 2846 | + Generic class ``IO[AnyStr]`` and its subclasses ``TextIO(IO[str])`` | ||
| 2845 | 2847 | and ``BinaryIO(IO[bytes])`` | |
| 2846 | 2848 | 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 | + ... | ||
| 2848 | 2866 | ||
| 2849 | 2867 | Functions and decorators | |
| 2850 | 2868 | ------------------------ | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -619,6 +619,11 @@ io | |||
| 619 | 619 | :exc:`BlockingIOError` if the operation cannot immediately return bytes. | |
| 620 | 620 | (Contributed by Giovanni Siragusa in :gh:`109523`.) | |
| 621 | 621 | ||
| 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 | + | ||
| 622 | 627 | ||
| 623 | 628 | json | |
| 624 | 629 | ---- | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -16,7 +16,7 @@ | |||
| 16 | 16 | _setmode = None | |
| 17 | 17 | ||
| 18 | 18 | import io | |
| 19 | - from io import (__all__, SEEK_SET, SEEK_CUR, SEEK_END) # noqa: F401 | ||
| 19 | + from io import (__all__, SEEK_SET, SEEK_CUR, SEEK_END, Reader, Writer) # noqa: F401 | ||
| 20 | 20 | ||
| 21 | 21 | valid_seek_flags = {0, 1, 2} # Hardwired values | |
| 22 | 22 | if hasattr(os, 'SEEK_HOLE') : | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -46,12 +46,14 @@ | |||
| 46 | 46 | "BufferedReader", "BufferedWriter", "BufferedRWPair", | |
| 47 | 47 | "BufferedRandom", "TextIOBase", "TextIOWrapper", | |
| 48 | 48 | "UnsupportedOperation", "SEEK_SET", "SEEK_CUR", "SEEK_END", | |
| 49 | - "DEFAULT_BUFFER_SIZE", "text_encoding", "IncrementalNewlineDecoder"] | ||
| 49 | + "DEFAULT_BUFFER_SIZE", "text_encoding", "IncrementalNewlineDecoder", | ||
| 50 | + "Reader", "Writer"] | ||
| 50 | 51 | ||
| 51 | 52 | ||
| 52 | 53 | import _io | |
| 53 | 54 | import abc | |
| 54 | 55 | ||
| 56 | + from _collections_abc import _check_methods | ||
| 55 | 57 | from _io import (DEFAULT_BUFFER_SIZE, BlockingIOError, UnsupportedOperation, | |
| 56 | 58 | open, open_code, FileIO, BytesIO, StringIO, BufferedReader, | |
| 57 | 59 | BufferedWriter, BufferedRWPair, BufferedRandom, | |
@@ -97,3 +99,55 @@ class TextIOBase(_io._TextIOBase, IOBase): | |||
| 97 | 99 | pass | |
| 98 | 100 | else: | |
| 99 | 101 | RawIOBase.register(_WindowsConsoleIO) | |
| 102 | + | ||
| 103 | + # | ||
| 104 | + # Static Typing Support | ||
| 105 | + # | ||
| 106 | + | ||
| 107 | + GenericAlias = type(list[int]) | ||
| 108 | + | ||
| 109 | + | ||
| 110 | + class Reader(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 | + def read(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 | + if cls is Reader: | ||
| 129 | + return _check_methods(C, "read") | ||
| 130 | + return NotImplemented | ||
| 131 | + | ||
| 132 | + __class_getitem__ = classmethod(GenericAlias) | ||
| 133 | + | ||
| 134 | + | ||
| 135 | + class Writer(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 | + def write(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 | + if cls is Writer: | ||
| 150 | + return _check_methods(C, "write") | ||
| 151 | + return NotImplemented | ||
| 152 | + | ||
| 153 | + __class_getitem__ = classmethod(GenericAlias) | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -4916,6 +4916,24 @@ class PySignalsTest(SignalsTest): | |||
| 4916 | 4916 | test_reentrant_write_text = None | |
| 4917 | 4917 | ||
| 4918 | 4918 | ||
| 4919 | + class ProtocolsTest(unittest.TestCase): | ||
| 4920 | + class MyReader: | ||
| 4921 | + def read(self, sz=-1): | ||
| 4922 | + return b"" | ||
| 4923 | + | ||
| 4924 | + class MyWriter: | ||
| 4925 | + def write(self, b: bytes): | ||
| 4926 | + pass | ||
| 4927 | + | ||
| 4928 | + def test_reader_subclass(self): | ||
| 4929 | + self.assertIsSubclass(MyReader, io.Reader[bytes]) | ||
| 4930 | + self.assertNotIsSubclass(str, io.Reader[bytes]) | ||
| 4931 | + | ||
| 4932 | + def test_writer_subclass(self): | ||
| 4933 | + self.assertIsSubclass(MyWriter, io.Writer[bytes]) | ||
| 4934 | + self.assertNotIsSubclass(str, io.Writer[bytes]) | ||
| 4935 | + | ||
| 4936 | + | ||
| 4919 | 4937 | def load_tests(loader, tests, pattern): | |
| 4920 | 4938 | tests = (CIOTest, PyIOTest, APIMismatchTest, | |
| 4921 | 4939 | CBufferedReaderTest, PyBufferedReaderTest, | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -6,6 +6,7 @@ | |||
| 6 | 6 | from functools import lru_cache, wraps, reduce | |
| 7 | 7 | import gc | |
| 8 | 8 | import inspect | |
| 9 | + import io | ||
| 9 | 10 | import itertools | |
| 10 | 11 | import operator | |
| 11 | 12 | import os | |
@@ -4294,6 +4295,40 @@ def __release_buffer__(self, mv: memoryview) -> None: | |||
| 4294 | 4295 | self.assertNotIsSubclass(C, ReleasableBuffer) | |
| 4295 | 4296 | self.assertNotIsInstance(C(), ReleasableBuffer) | |
| 4296 | 4297 | ||
| 4298 | + def test_io_reader_protocol_allowed(self): | ||
| 4299 | + @runtime_checkable | ||
| 4300 | + class CustomReader(io.Reader[bytes], Protocol): | ||
| 4301 | + def close(self): ... | ||
| 4302 | + | ||
| 4303 | + class A: pass | ||
| 4304 | + class B: | ||
| 4305 | + def read(self, sz=-1): | ||
| 4306 | + return b"" | ||
| 4307 | + def close(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 | + def test_io_writer_protocol_allowed(self): | ||
| 4316 | + @runtime_checkable | ||
| 4317 | + class CustomWriter(io.Writer[bytes], Protocol): | ||
| 4318 | + def close(self): ... | ||
| 4319 | + | ||
| 4320 | + class A: pass | ||
| 4321 | + class B: | ||
| 4322 | + def write(self, b): | ||
| 4323 | + pass | ||
| 4324 | + def close(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 | + | ||
| 4297 | 4332 | def test_builtin_protocol_allowlist(self): | |
| 4298 | 4333 | with self.assertRaises(TypeError): | |
| 4299 | 4334 | class CustomProtocol(TestCase, Protocol): | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1876,6 +1876,7 @@ def _allow_reckless_class_checks(depth=2): | |||
| 1876 | 1876 | 'Reversible', 'Buffer', | |
| 1877 | 1877 | ], | |
| 1878 | 1878 | 'contextlib': ['AbstractContextManager', 'AbstractAsyncContextManager'], | |
| 1879 | + 'io': ['Reader', 'Writer'], | ||
| 1879 | 1880 | 'os': ['PathLike'], | |
| 1880 | 1881 | } | |
| 1881 | 1882 | ||
| Original file line number | Diff line number | Diff 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`. | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments