From f248baf418c49e5748bd503dd992b52440ed236b Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Sun, 23 May 2021 22:12:13 -0300 Subject: [PATCH] adding sentinel metaclass example --- 08-def-type-hints/typevars_constrained.py | 26 +++++++++ 15-more-types/{ => cafeteria}/cafeteria.py | 2 +- 15-more-types/cafeteria/contravariant.py | 45 ++++++++++++++++ 15-more-types/cafeteria/covariant.py | 48 +++++++++++++++++ 15-more-types/cafeteria/invariant.py | 54 +++++++++++++++++++ .../clip_annot.py | 0 .../clip_annot_1ed.py | 0 .../clip_annot_signature.rst | 0 .../mymax}/mymax.py | 0 .../mymax}/mymax_demo.py | 0 .../mymax}/mymax_test.py | 0 {08-def-type-hints => 15-more-types}/mysum.py | 0 25-class-metaprog/sentinel/sentinel.py | 24 +++++++++ 25-class-metaprog/sentinel/sentinel_test.py | 22 ++++++++ 14 files changed, 220 insertions(+), 1 deletion(-) create mode 100644 08-def-type-hints/typevars_constrained.py rename 15-more-types/{ => cafeteria}/cafeteria.py (98%) create mode 100644 15-more-types/cafeteria/contravariant.py create mode 100644 15-more-types/cafeteria/covariant.py create mode 100644 15-more-types/cafeteria/invariant.py rename {08-def-type-hints => 15-more-types}/clip_annot.py (100%) rename {08-def-type-hints => 15-more-types}/clip_annot_1ed.py (100%) rename {08-def-type-hints => 15-more-types}/clip_annot_signature.rst (100%) rename {08-def-type-hints/comparable => 15-more-types/mymax}/mymax.py (100%) rename {08-def-type-hints/comparable => 15-more-types/mymax}/mymax_demo.py (100%) rename {08-def-type-hints/comparable => 15-more-types/mymax}/mymax_test.py (100%) rename {08-def-type-hints => 15-more-types}/mysum.py (100%) create mode 100644 25-class-metaprog/sentinel/sentinel.py create mode 100644 25-class-metaprog/sentinel/sentinel_test.py diff --git a/08-def-type-hints/typevars_constrained.py b/08-def-type-hints/typevars_constrained.py new file mode 100644 index 0000000..2bfea13 --- /dev/null +++ b/08-def-type-hints/typevars_constrained.py @@ -0,0 +1,26 @@ +from typing import TypeVar, TYPE_CHECKING +from decimal import Decimal + +# tag::TYPEVAR_RESTRICTED[] +RT = TypeVar('RT', float, Decimal) + +def triple1(a: RT) -> RT: + return a * 3 + +res1 = triple1(1, 2) + +if TYPE_CHECKING: + reveal_type(res1) +# end::TYPEVAR_RESTRICTED[] + +# tag::TYPEVAR_BOUNDED[] +BT = TypeVar('BT', bound=float) + +def triple2(a: BT) -> BT: + return a * 3 + +res2 = triple2(1, 2) + +if TYPE_CHECKING: + reveal_type(res2) +# tag::TYPEVAR_BOUNDED[] diff --git a/15-more-types/cafeteria.py b/15-more-types/cafeteria/cafeteria.py similarity index 98% rename from 15-more-types/cafeteria.py rename to 15-more-types/cafeteria/cafeteria.py index 622f8e4..36769df 100644 --- a/15-more-types/cafeteria.py +++ b/15-more-types/cafeteria/cafeteria.py @@ -41,7 +41,7 @@ T_contra = TypeVar('T_contra', contravariant=True) class TrashCan(Generic[T_contra]): def put(self, trash: T_contra) -> None: - """Store trash until dumped...""" + """Store trash until dumped.""" class Cafeteria: diff --git a/15-more-types/cafeteria/contravariant.py b/15-more-types/cafeteria/contravariant.py new file mode 100644 index 0000000..399a748 --- /dev/null +++ b/15-more-types/cafeteria/contravariant.py @@ -0,0 +1,45 @@ +# tag::TRASH_TYPES[] +from typing import TypeVar, Generic + +class Refuse: # <1> + """Any refuse.""" + +class Biodegradable(Refuse): + """Biodegradable refuse.""" + +class Compostable(Biodegradable): + """Compostable refuse.""" + +T_contra = TypeVar('T_contra', contravariant=True) # <2> + +class TrashCan(Generic[T_contra]): # <3> + def put(self, refuse: T_contra) -> None: + """Store trash until dumped.""" + +def deploy(trash_can: TrashCan[Biodegradable]): + """Deploy a trash can for biodegradable refuse.""" +# end::TRASH_TYPES[] + + +################################################ contravariant trash can + + +# tag::DEPLOY_TRASH_CANS[] +bio_can: TrashCan[Biodegradable] = TrashCan() +deploy(bio_can) + +trash_can: TrashCan[Refuse] = TrashCan() +deploy(trash_can) +# end::DEPLOY_TRASH_CANS[] + + +################################################ more specific trash can + +# tag::DEPLOY_NOT_VALID[] +compost_can: TrashCan[Compostable] = TrashCan() +deploy(compost_can) +# end::DEPLOY_NOT_VALID[] + +## Argument 1 to "deploy" has +## incompatible type "TrashCan[Compostable]" +## expected "TrashCan[Biodegradable]" diff --git a/15-more-types/cafeteria/covariant.py b/15-more-types/cafeteria/covariant.py new file mode 100644 index 0000000..87879dd --- /dev/null +++ b/15-more-types/cafeteria/covariant.py @@ -0,0 +1,48 @@ +from typing import TypeVar, Generic + + +class Beverage: + """Any beverage.""" + + +class Juice(Beverage): + """Any fruit juice.""" + + +class OrangeJuice(Juice): + """Delicious juice from Brazilian oranges.""" + + +# tag::BEVERAGE_TYPES[] +T_co = TypeVar('T_co', covariant=True) # <1> + + +class BeverageDispenser(Generic[T_co]): # <2> + def __init__(self, beverage: T_co) -> None: + self.beverage = beverage + + def dispense(self) -> T_co: + return self.beverage + +def install(dispenser: BeverageDispenser[Juice]) -> None: # <3> + """Install a fruit juice dispenser.""" +# end::BEVERAGE_TYPES[] + +################################################ covariant dispenser + +# tag::INSTALL_JUICE_DISPENSERS[] +juice_dispenser = BeverageDispenser(Juice()) +install(juice_dispenser) + +orange_juice_dispenser = BeverageDispenser(OrangeJuice()) +install(orange_juice_dispenser) +# end::INSTALL_JUICE_DISPENSERS[] + +################################################ not a juice dispenser + +beverage_dispenser = BeverageDispenser(Beverage()) + +## Argument 1 to "install" has +## incompatible type "BeverageDispenser[Beverage]" +## expected "BeverageDispenser[Juice]" +install(beverage_dispenser) diff --git a/15-more-types/cafeteria/invariant.py b/15-more-types/cafeteria/invariant.py new file mode 100644 index 0000000..1f9d6f6 --- /dev/null +++ b/15-more-types/cafeteria/invariant.py @@ -0,0 +1,54 @@ +# tag::BEVERAGE_TYPES[] +from typing import TypeVar, Generic + +class Beverage: # <1> + """Any beverage.""" + +class Juice(Beverage): + """Any fruit juice.""" + +class OrangeJuice(Juice): + """Delicious juice from Brazilian oranges.""" + +T = TypeVar('T') # <2> + +class BeverageDispenser(Generic[T]): # <3> + """A dispenser parameterized on the beverage type.""" + def __init__(self, beverage: T) -> None: + self.beverage = beverage + + def dispense(self) -> T: + return self.beverage + +def install(dispenser: BeverageDispenser[Juice]) -> None: # <4> + """Install a fruit juice dispenser.""" +# end::BEVERAGE_TYPES[] + +################################################ exact type + +# tag::INSTALL_JUICE_DISPENSER[] +juice_dispenser = BeverageDispenser(Juice()) +install(juice_dispenser) +# end::INSTALL_JUICE_DISPENSER[] + + +################################################ variant dispenser + +# tag::INSTALL_BEVERAGE_DISPENSER[] +beverage_dispenser = BeverageDispenser(Beverage()) +install(beverage_dispenser) +## Argument 1 to "install" has +## incompatible type "BeverageDispenser[Beverage]" +## expected "BeverageDispenser[Juice]" +# end::INSTALL_BEVERAGE_DISPENSER[] + + +################################################ variant dispenser + +# tag::INSTALL_ORANGE_JUICE_DISPENSER[] +orange_juice_dispenser = BeverageDispenser(OrangeJuice()) +install(orange_juice_dispenser) +# end::INSTALL_ORANGE_JUICE_DISPENSER[] +## Argument 1 to "install" has +## incompatible type "BeverageDispenser[OrangeJuice]" +## expected "BeverageDispenser[Juice]" diff --git a/08-def-type-hints/clip_annot.py b/15-more-types/clip_annot.py similarity index 100% rename from 08-def-type-hints/clip_annot.py rename to 15-more-types/clip_annot.py diff --git a/08-def-type-hints/clip_annot_1ed.py b/15-more-types/clip_annot_1ed.py similarity index 100% rename from 08-def-type-hints/clip_annot_1ed.py rename to 15-more-types/clip_annot_1ed.py diff --git a/08-def-type-hints/clip_annot_signature.rst b/15-more-types/clip_annot_signature.rst similarity index 100% rename from 08-def-type-hints/clip_annot_signature.rst rename to 15-more-types/clip_annot_signature.rst diff --git a/08-def-type-hints/comparable/mymax.py b/15-more-types/mymax/mymax.py similarity index 100% rename from 08-def-type-hints/comparable/mymax.py rename to 15-more-types/mymax/mymax.py diff --git a/08-def-type-hints/comparable/mymax_demo.py b/15-more-types/mymax/mymax_demo.py similarity index 100% rename from 08-def-type-hints/comparable/mymax_demo.py rename to 15-more-types/mymax/mymax_demo.py diff --git a/08-def-type-hints/comparable/mymax_test.py b/15-more-types/mymax/mymax_test.py similarity index 100% rename from 08-def-type-hints/comparable/mymax_test.py rename to 15-more-types/mymax/mymax_test.py diff --git a/08-def-type-hints/mysum.py b/15-more-types/mysum.py similarity index 100% rename from 08-def-type-hints/mysum.py rename to 15-more-types/mysum.py diff --git a/25-class-metaprog/sentinel/sentinel.py b/25-class-metaprog/sentinel/sentinel.py new file mode 100644 index 0000000..bc94e5d --- /dev/null +++ b/25-class-metaprog/sentinel/sentinel.py @@ -0,0 +1,24 @@ +""" + + >>> class Missing(Sentinel): pass + >>> Missing + + >>> class CustomRepr(Sentinel): + ... repr = '*** sentinel ***' + ... + >>> CustomRepr + *** sentinel *** + +""" + +class SentinelMeta(type): + def __repr__(cls): + try: + return cls.repr + except AttributeError: + return f'<{cls.__name__}>' + +class Sentinel(metaclass=SentinelMeta): + def __new__(cls): + return cls + diff --git a/25-class-metaprog/sentinel/sentinel_test.py b/25-class-metaprog/sentinel/sentinel_test.py new file mode 100644 index 0000000..60a2919 --- /dev/null +++ b/25-class-metaprog/sentinel/sentinel_test.py @@ -0,0 +1,22 @@ +from sentinel import Sentinel + +class PlainSentinel(Sentinel): pass + + +class SentinelCustomRepr(Sentinel): + repr = '***SentinelRepr***' + + +def test_repr(): + assert repr(PlainSentinel) == '' + + +def test_pickle(): + from pickle import dumps, loads + s = dumps(PlainSentinel) + ps = loads(s) + assert ps is PlainSentinel + +def test_custom_repr(): + assert repr(SentinelCustomRepr) == '***SentinelRepr***' + \ No newline at end of file