-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
4f94d6d
commit 38f743c
Showing
12 changed files
with
329 additions
and
66 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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'<Order total: {self.total():.2f} due: {self.due():.2f}>' | ||
|
||
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 total: 42.00 due: 42.00> | ||
>>> Order(ann, cart, fidelity_promo) | ||
<Order total: 42.00 due: 39.90> | ||
>>> banana_cart = [LineItem('banana', 30, Decimal('.5')), | ||
... LineItem('apple', 10, Decimal('1.5'))] | ||
>>> Order(joe, banana_cart, bulk_item_promo) | ||
<Order total: 30.00 due: 28.50> | ||
>>> long_cart = [LineItem(str(item_code), 1, Decimal(1)) | ||
... for item_code in range(10)] | ||
>>> Order(joe, long_cart, large_order_promo) | ||
<Order total: 10.00 due: 9.30> | ||
>>> Order(joe, cart, large_order_promo) | ||
<Order total: 42.00 due: 42.00> | ||
|
||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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(<function f1 at 0x100631bf8>) | ||
running register(<function f2 at 0x100631c80>) | ||
running main() | ||
registry -> [<function f1 at 0x100631bf8>, <function f2 at 0x100631c80>] | ||
running f1() | ||
running f2() | ||
running f3() | ||
``` | ||
|
||
```python | ||
>>> import registration | ||
running register(<function f1 at 0x10063b1e0>) | ||
running register(<function f2 at 0x10063b268>) | ||
>>> registration.registry | ||
[<function f1 at 0x10063b1e0>, <function f2 at 0x10063b268>] | ||
``` | ||
- [[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<br> | ||
> * A decorator may replace decorated function with a different one<br> | ||
> * Decorators are executed immediately when a module is loaded<br> | ||
> Ramlho, 2022, p306 | ||
- [[o8vo-decorators-strategy]]# | ||
|
||
### References | ||
- Ramalho, 2022, p302-307 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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'<Order total: {self.total():.2f} due: {self.due():.2f}>' | ||
|
||
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 total: 42.00 due: 42.00> | ||
>>> Order(ann, cart, FidelityPromo()) | ||
<Order total: 42.00 due: 39.90> | ||
>>> banana_cart = (LineItem('banana', 30, Decimal('.5')), | ||
... LineItem('apple', 10, Decimal('1.5'))) | ||
>>> Order(joe, banana_cart, BulkItemPromo()) | ||
<Order total: 30.00 due: 28.50> | ||
>>> long_cart = tuple(LineItem(str(sku), 1, Decimal(1)) | ||
... for sku in range(10)) | ||
>>> Order(joe, long_cart, LargeOrderPromo()) | ||
<Order total: 10.00 due: 9.30> | ||
>>> Order(joe, cart, LargeOrderPromo()) | ||
<Order total: 42.00 due: 42.00> | ||
``` | ||
- 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]]# |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.