diff --git a/2ot8-python-strategy-with-functions.md b/2ot8-python-strategy-with-functions.md new file mode 100644 index 0000000..be3805b --- /dev/null +++ b/2ot8-python-strategy-with-functions.md @@ -0,0 +1,80 @@ +# Python Strategy with Functions +#designpattern #datastructure #class #apply #python #programming + +- A more concise code compared to the #[[python-strategy]] using [[a8bt-high-order-functions]] + +```python +from collections.abc import Sequence +from dataclasses import dataclass +from decimal import Decimal +from typing import Optional, Callable, NamedTuple +class Customer(NamedTuple): + name: str + fidelity: int + +class LineItem(NamedTuple): + product: str + quantity: int + price: Decimal + def total(self): + return self.price * self.quantity + +@dataclass(frozen=True) +class Order: # the Context + customer: Customer + cart: Sequence[LineItem] + promotion: Optional[Callable[['Order'], Decimal]] = None + def total(self) -> Decimal: + totals = (item.total() for item in self.cart) + return sum(totals, start=Decimal(0)) + def due(self) -> Decimal: + if self.promotion is None: + discount = Decimal(0) + else: + discount = self.promotion(self) + return self.total() - discount + def __repr__(self): + return f'' + +def fidelity_promo(order: Order) -> Decimal: + """5% discount for customers with 1000 or more fidelity points""" + if order.customer.fidelity >= 1000: + return order.total() * Decimal('0.05') + return Decimal(0) + +def bulk_item_promo(order: Order) -> Decimal: + """10% discount for each LineItem with 20 or more units""" + discount = Decimal(0) + for item in order.cart: + if item.quantity >= 20: + discount += item.total() * Decimal('0.1') + return discount + +def large_order_promo(order: Order) -> Decimal: + """7% discount for orders with 10 or more distinct items""" + distinct_items = {item.product for item in order.cart} + if len(distinct_items) >= 10: + return order.total() * Decimal('0.07') + return Decimal(0) + +>>> joe = Customer('John Doe', 0) +>>> ann = Customer('Ann Smith', 1100) +>>> cart = [LineItem('banana', 4, Decimal('.5')), +... LineItem('apple', 10, Decimal('1.5')), +... LineItem('watermelon', 5, Decimal(5))] +>>> Order(joe, cart, fidelity_promo) + +>>> Order(ann, cart, fidelity_promo) + +>>> banana_cart = [LineItem('banana', 30, Decimal('.5')), +... LineItem('apple', 10, Decimal('1.5'))] +>>> Order(joe, banana_cart, bulk_item_promo) + +>>> long_cart = [LineItem(str(item_code), 1, Decimal(1)) +... for item_code in range(10)] +>>> Order(joe, long_cart, large_order_promo) + +>>> Order(joe, cart, large_order_promo) + + +``` diff --git a/aswx-python-decorator-implementation-with-parameters.md b/aswx-python-decorator-implementation-with-parameters.md index 1ab89dd..150f360 100644 --- a/aswx-python-decorator-implementation-with-parameters.md +++ b/aswx-python-decorator-implementation-with-parameters.md @@ -1,4 +1,4 @@ -# Python Decorator Implementation with Parameters +# Python Decorator Clock Implementation with Parameters #designpattern #python #decorator ```python diff --git a/cea0-decorators.md b/cea0-decorators.md index 5210008..8429f18 100644 --- a/cea0-decorators.md +++ b/cea0-decorators.md @@ -1,70 +1,13 @@ # Python Decorators -#python #designpattern #programming #decorator +#python #designpattern #programming #decorator #index -- Is a type of #[[dcdy-callable-objects]] -- The objective: Decorate another function and take this function as argument -- Most part of the decorators change the function decorated -- Decorators are defined in one module and used and other modules as well -- They run on the step of `import time` - -```python -@decorate -def target(): - print("running target") -``` -- Code above transform into: `target = decorate(target)` - -```python -#file registration.py -registry = [] -def register(func): - print(f'running register({func})') - registry.append(func) - return func -@register -def f1(): - print('running f1()') -@register -def f2(): - print('running f2()') -def f3(): - print('running f3()') -def main(): - print('running main()') - print('registry ->', registry) - f1() - f2() - f3() -if __name__ == '__main__': - main() - -$python registration.py -running register() -running register() -running main() -registry -> [, ] -running f1() -running f2() -running f3() -``` - -```python ->>> import registration -running register() -running register() ->>> registration.registry -[, ] -``` +- [[z2az-python-decorator-concept]]# +- [[x6ii-register-decorator]]# - [[wmjn-python-decorator-implementation]]# - [[aswx-python-decorator-implementation-with-parameters]]# - [[naei-cache-lru-cache-singledispatch]]# - [[hl16-parameterized-decorators]]# - -> [!tip] Quote -> * A decorator is a function or another callable
-> * A decorator may replace decorated function with a different one
-> * Decorators are executed immediately when a module is loaded
-> Ramlho, 2022, p306 +- [[o8vo-decorators-strategy]]# ### References - Ramalho, 2022, p302-307 diff --git a/hl16-parameterized-decorators.md b/hl16-parameterized-decorators.md index 9b85fb9..e434bf9 100644 --- a/hl16-parameterized-decorators.md +++ b/hl16-parameterized-decorators.md @@ -1,5 +1,5 @@ # Parameterized Decorators -#python #decorator #designpattern #programming +#python #decorator #designpattern #programming #apply - To create a decorator with parameters is necessary to build a **decorator factory** - This decorator factory will accept parameter and pass to the decorator itself diff --git a/naei-cache-lru-cache-singledispatch.md b/naei-cache-lru-cache-singledispatch.md index 3b25b49..3e15f50 100644 --- a/naei-cache-lru-cache-singledispatch.md +++ b/naei-cache-lru-cache-singledispatch.md @@ -1,5 +1,5 @@ # cache, lru-cache, singledispatch -#python #efficiency #decorator #designpattern #programming +#python #efficiency #decorator #designpattern #programming #overriding ## Cache @@ -40,5 +40,4 @@ if __name__ == '__main__': - [[rugi-singledispatch-implementation]]# ### Reference - - Ramalho, 2022, p320-329 diff --git a/o8vo-decorators-strategy.md b/o8vo-decorators-strategy.md new file mode 100644 index 0000000..6886de5 --- /dev/null +++ b/o8vo-decorators-strategy.md @@ -0,0 +1,44 @@ +# Python Decorators + Strategy +#designpattern #python #programming #apply #datastructure #combination + + +- The decorator is used to register each promo type into a list, so in that way we can select the best promo to apply + for each person + +```python +Promotion = Callable[[Order], Decimal] + +promos: list[Promotion] = [] + +def promotion(promo: Promotion) -> Promotion: + promos.append(promo) + return promo + +def best_promo(order: Order) -> Decimal: + """Compute the best discount available""" + return max(promo(order) for promo in promos) + +@promotion +def fidelity(order: Order) -> Decimal: + """5% discount for customers with 1000 or more fidelity points""" + if order.customer.fidelity >= 1000: + return order.total() * Decimal('0.05') + return Decimal(0) + +@promotion +def bulk_item(order: Order) -> Decimal: + """10% discount for each LineItem with 20 or more units""" + discount = Decimal(0) + for item in order.cart: + if item.quantity >= 20: + discount += item.total() * Decimal('0.1') + return discount + +@promotion +def large_order(order: Order) -> Decimal: + """7% discount for orders with 10 or more distinct items""" + distinct_items = {item.product for item in order.cart} + if len(distinct_items) >= 10: + return order.total() * Decimal('0.07') + return Decimal(0) +``` diff --git a/otjt-python-strategy.md b/otjt-python-strategy.md new file mode 100644 index 0000000..8265eb4 --- /dev/null +++ b/otjt-python-strategy.md @@ -0,0 +1,119 @@ +# Python Strategy +#designpattern #datastructure #programming #python #class #apply + + + +```mermaid +--- +title: Strategy Promotion Example (Ramalho, 2022, p343) +--- +classDiagram + Promotion <|-- LargeOrderPromo + Promotion <|-- FidelityPromo + Promotion <|-- BulkItemPromo + Promotion <--o Order: Aggregation + Promotion: discount() + class Order { + total() + due() + } + class LargeOrderPromo{ + discount() + } + class FidelityPromo{ + discount() + } + class BulkItemPromo{ + discount() + } +``` + +```python +from abc import ABC, abstractmethod +from collections.abc import Sequence +from decimal import Decimal +from typing import NamedTuple, Optional + +class Customer(NamedTuple): + name: str + fidelity: int + +class LineItem(NamedTuple): + product: str + quantity: int + price: Decimal + def total(self) -> Decimal: + return self.price * self.quantity + +class Order(NamedTuple): # the Context + customer: Customer + cart: Sequence[LineItem] + promotion: Optional['Promotion'] = None + def total(self) -> Decimal: + totals = (item.total() for item in self.cart) + return sum(totals, start=Decimal(0)) + def due(self) -> Decimal: + if self.promotion is None: + discount = Decimal(0) + else: + discount = self.promotion.discount(self) + return self.total() - discount + def __repr__(self): + return f'' + +class Promotion(ABC): # the Strategy: an abstract base class + @abstractmethod + def discount(self, order: Order) -> Decimal: + """Return discount as a positive dollar amount""" + +class FidelityPromo(Promotion): # first Concrete Strategy + """5% discount for customers with 1000 or more fidelity points""" + def discount(self, order: Order) -> Decimal: + rate = Decimal('0.05') + if order.customer.fidelity >= 1000: + return order.total() * rate + return Decimal(0) + +class BulkItemPromo(Promotion): # second Concrete Strategy + """10% discount for each LineItem with 20 or more units""" + def discount(self, order: Order) -> Decimal: + discount = Decimal(0) + for item in order.cart: + if item.quantity >= 20: + discount += item.total() * Decimal('0.1') + return discount + +class LargeOrderPromo(Promotion): # third Concrete Strategy + """7% discount for orders with 10 or more distinct items""" + def discount(self, order: Order) -> Decimal: + distinct_items = {item.product for item in order.cart} + if len(distinct_items) >= 10: + return order.total() * Decimal('0.07') + return Decimal(0) + +>>> joe = Customer('John Doe', 0) +>>> ann = Customer('Ann Smith', 1100) +>>> cart = (LineItem('banana', 4, Decimal('.5')), +... LineItem('apple', 10, Decimal('1.5')), +... LineItem('watermelon', 5, Decimal(5))) +>>> Order(joe, cart, FidelityPromo()) + +>>> Order(ann, cart, FidelityPromo()) + +>>> banana_cart = (LineItem('banana', 30, Decimal('.5')), +... LineItem('apple', 10, Decimal('1.5'))) +>>> Order(joe, banana_cart, BulkItemPromo()) + +>>> long_cart = tuple(LineItem(str(sku), 1, Decimal(1)) +... for sku in range(10)) +>>> Order(joe, long_cart, LargeOrderPromo()) + +>>> Order(joe, cart, LargeOrderPromo()) + +``` +- As you can see the strategy concert class above only have one function. Given that, we can transform it to plain + functions instead of use classes. We can take benefit of high order functions ([[a8bt-high-order-functions]]) and implement it in a more concise way. + - [[2ot8-python-strategy-with-functions]]# and implement it in a more concise way and implement it in a more concise way. +- Usually `strategy` deal with data inside a context and does have internal state (instance attribute) + - In the code above the context is the class `Order` +- [[o8vo-decorators-strategy]]# diff --git a/tds4-python.md b/tds4-python.md index 19aa827..39e4a71 100644 --- a/tds4-python.md +++ b/tds4-python.md @@ -20,3 +20,4 @@ - [[dcdy-callable-objects]]# - [[135k-keyword-only-arguments]]# - [[7hky-python-variable-scope]]# +- [[zewz-design-patterns]]# diff --git a/wmjn-python-decorator-implementation.md b/wmjn-python-decorator-implementation.md index b033bcc..45b3c25 100644 --- a/wmjn-python-decorator-implementation.md +++ b/wmjn-python-decorator-implementation.md @@ -1,4 +1,4 @@ -# Python Decorator Implementation +# Python Decorator Clock Implementation #decorator #designpattern #apply #python #programming ```python diff --git a/x6ii-register-decorator.md b/x6ii-register-decorator.md new file mode 100644 index 0000000..788df5d --- /dev/null +++ b/x6ii-register-decorator.md @@ -0,0 +1,54 @@ +# Register Decorator +#python #designpattern #programming #decorator #apply + +```python +@decorate +def target(): + print("running target") +``` +- Code above transform into: `target = decorate(target)` + +```python +#file registration.py +registry = [] +def register(func): + print(f'running register({func})') + registry.append(func) + return func +@register +def f1(): + print('running f1()') +@register +def f2(): + print('running f2()') +def f3(): + print('running f3()') +def main(): + print('running main()') + print('registry ->', registry) + f1() + f2() + f3() +if __name__ == '__main__': + main() + +$python registration.py +running register() +running register() +running main() +registry -> [, ] +running f1() +running f2() +running f3() +``` + +```python +>>> import registration +running register() +running register() +>>> registration.registry +[, ] +``` + +### References +- Ramalho, 2022, p302-307 diff --git a/z2az-python-decorator-concept.md b/z2az-python-decorator-concept.md new file mode 100644 index 0000000..8c4234a --- /dev/null +++ b/z2az-python-decorator-concept.md @@ -0,0 +1,19 @@ +# Python Decorator Concept +#python #designpattern #programming #decorator #theory #tip + + +- Is a type of #[[dcdy-callable-objects]] +- The objective: Decorate another function and take this function as argument +- Most part of the decorators change the function decorated +- Decorators are defined in one module and used and other modules as well +- They run on the step of `import time` + +> [!tip] Quote +> * A decorator is a function or another callable
+> * A decorator may replace decorated function with a different one
+> * Decorators are executed immediately when a module is loaded
+> Ramalho, 2022, p306 + + +### References +- Ramalho, 2022, p302-307 diff --git a/zewz-design-patterns.md b/zewz-design-patterns.md new file mode 100644 index 0000000..ce03ac2 --- /dev/null +++ b/zewz-design-patterns.md @@ -0,0 +1,4 @@ +# Design Patterns + +- [[cea0-decorators]]# +- [[otjt-python-strategy]]#