From 0ce109a9fef0e1a71576268b6469aa2b02fe3a9c Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Mon, 24 May 2021 13:27:07 -0300 Subject: [PATCH 1/4] improved sentinel after learning from @taleinat on python-dev --- 25-class-metaprog/sentinel/sentinel.py | 26 +++++++++++++++++---- 25-class-metaprog/sentinel/sentinel_test.py | 21 +++++++++++++---- 2 files changed, 38 insertions(+), 9 deletions(-) diff --git a/25-class-metaprog/sentinel/sentinel.py b/25-class-metaprog/sentinel/sentinel.py index 20fcc7c..1cce65e 100644 --- a/25-class-metaprog/sentinel/sentinel.py +++ b/25-class-metaprog/sentinel/sentinel.py @@ -1,23 +1,41 @@ """ +This module provides a ``Sentinel`` class that can be used directly as a +sentinel singleton, or subclassed if a distinct sentinel singleton is needed. + +The ``repr`` of a ``Sentinel`` class is its name:: >>> class Missing(Sentinel): pass >>> Missing Missing + +If a different ``repr`` is required, +you can define it as a class attribute:: + >>> class CustomRepr(Sentinel): ... repr = '' ... >>> CustomRepr +``Sentinel`` classes cannot be instantiated:: + + >>> Missing() + Traceback (most recent call last): + ... + TypeError: 'Missing' is a sentinel and cannot be instantiated + """ -class SentinelMeta(type): + +class _SentinelMeta(type): def __repr__(cls): try: return cls.repr except AttributeError: - return cls.__name__ + return f'{cls.__name__}' -class Sentinel(metaclass=SentinelMeta): + +class Sentinel(metaclass=_SentinelMeta): def __new__(cls): - return cls + msg = 'is a sentinel and cannot be instantiated' + raise TypeError(f"'{cls!r}' {msg}") diff --git a/25-class-metaprog/sentinel/sentinel_test.py b/25-class-metaprog/sentinel/sentinel_test.py index 4142289..5e304b7 100644 --- a/25-class-metaprog/sentinel/sentinel_test.py +++ b/25-class-metaprog/sentinel/sentinel_test.py @@ -1,8 +1,12 @@ import pickle +import pytest + from sentinel import Sentinel -class PlainSentinel(Sentinel): pass + +class PlainSentinel(Sentinel): + pass class SentinelCustomRepr(Sentinel): @@ -13,16 +17,23 @@ def test_repr(): assert repr(PlainSentinel) == 'PlainSentinel' -def test_pickle(): - s = pickle.dumps(PlainSentinel) - ps = pickle.loads(s) - assert ps is PlainSentinel +def test_cannot_instantiate(): + with pytest.raises(TypeError) as e: + PlainSentinel() + msg = "'PlainSentinel' is a sentinel and cannot be instantiated" + assert msg in str(e.value) def test_custom_repr(): assert repr(SentinelCustomRepr) == '***SentinelRepr***' +def test_pickle(): + s = pickle.dumps(SentinelCustomRepr) + ps = pickle.loads(s) + assert ps is SentinelCustomRepr + + def test_sentinel_comes_ready_to_use(): assert repr(Sentinel) == 'Sentinel' s = pickle.dumps(Sentinel) From e6e79b75d717038249e0988281b4a929608408e1 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Mon, 24 May 2021 13:35:53 -0300 Subject: [PATCH 2/4] removed unnecessary f-string --- 25-class-metaprog/sentinel/sentinel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/25-class-metaprog/sentinel/sentinel.py b/25-class-metaprog/sentinel/sentinel.py index 1cce65e..5cae279 100644 --- a/25-class-metaprog/sentinel/sentinel.py +++ b/25-class-metaprog/sentinel/sentinel.py @@ -32,7 +32,7 @@ class _SentinelMeta(type): try: return cls.repr except AttributeError: - return f'{cls.__name__}' + return cls.__name__ class Sentinel(metaclass=_SentinelMeta): From 1fffea6244fb07cce222de598959a41debe49530 Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Mon, 24 May 2021 13:48:55 -0300 Subject: [PATCH 3/4] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d0b57b3..7d786e1 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Fluent Python 2e example code -Example code for the book **Fluent Python, 2nd edition** by Luciano Ramalho (O'Reilly, 2020). +Example code for the book **Fluent Python, 2nd edition** by Luciano Ramalho (O'Reilly, 2021). > **BEWARE**: This is a work in progress! > From abf7ac2977777764759e199fc1a1b415966299da Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Wed, 26 May 2021 19:19:24 -0300 Subject: [PATCH 4/4] ch15: typing.cast example --- 15-more-types/cast/tcp_echo.py | 34 ++++++++++++++++++++++++++ 15-more-types/cast/tcp_echo_no_cast.py | 29 ++++++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 15-more-types/cast/tcp_echo.py create mode 100644 15-more-types/cast/tcp_echo_no_cast.py diff --git a/15-more-types/cast/tcp_echo.py b/15-more-types/cast/tcp_echo.py new file mode 100644 index 0000000..d0f078a --- /dev/null +++ b/15-more-types/cast/tcp_echo.py @@ -0,0 +1,34 @@ +import asyncio + +from asyncio import StreamReader, StreamWriter +from asyncio.trsock import TransportSocket +from typing import cast + +async def handle_echo(reader: StreamReader, writer: StreamWriter) -> None: + data = await reader.read(100) + message = data.decode() + addr = writer.get_extra_info('peername') + + print(f"Received {message!r} from {addr!r}") + + print(f"Send: {message!r}") + writer.write(data) + await writer.drain() + + print("Close the connection") + writer.close() + +async def main() -> None: + server = await asyncio.start_server( + handle_echo, '127.0.0.1', 8888) + + # tag::CAST[] + socket_list = cast(tuple[TransportSocket, ...], server.sockets) + addr = socket_list[0].getsockname() + # end::CAST[] + print(f'Serving on {addr}') + + async with server: + await server.serve_forever() + +asyncio.run(main()) diff --git a/15-more-types/cast/tcp_echo_no_cast.py b/15-more-types/cast/tcp_echo_no_cast.py new file mode 100644 index 0000000..168db55 --- /dev/null +++ b/15-more-types/cast/tcp_echo_no_cast.py @@ -0,0 +1,29 @@ +import asyncio + +from asyncio import StreamReader, StreamWriter + +async def handle_echo(reader: StreamReader, writer: StreamWriter) -> None: + data = await reader.read(100) + message = data.decode() + addr = writer.get_extra_info('peername') + + print(f"Received {message!r} from {addr!r}") + + print(f"Send: {message!r}") + writer.write(data) + await writer.drain() + + print("Close the connection") + writer.close() + +async def main() -> None: + server = await asyncio.start_server( + handle_echo, '127.0.0.1', 8888) + + addr = server.sockets[0].getsockname() + print(f'Serving on {addr}') + + async with server: + await server.serve_forever() + +asyncio.run(main())