From 0ce109a9fef0e1a71576268b6469aa2b02fe3a9c Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Mon, 24 May 2021 13:27:07 -0300 Subject: [PATCH] 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)