일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- 컴퓨터의 구조
- Protocol
- 715. Range Module
- kaggle
- Decorator
- Generator
- shiba
- DWG
- 파이썬
- 43. Multiply Strings
- Python Implementation
- 109. Convert Sorted List to Binary Search Tree
- attribute
- Class
- 프로그래머스
- data science
- iterator
- Convert Sorted List to Binary Search Tree
- 315. Count of Smaller Numbers After Self
- 시바견
- LeetCode
- 30. Substring with Concatenation of All Words
- concurrency
- 운영체제
- Regular Expression
- 밴픽
- Python Code
- Substring with Concatenation of All Words
- t1
- Python
- Today
- Total
Scribbling
Python: Design Patterns 본문
Some traditional design patterns can be simplified by making good use of first-order functions in python.
Let's see the below example.
1. Goal: Calculate total price and discount for each order
2. There are three discount strategies;
- fidelitypromo: if the customer's fidelity >= 1000, then 5% of the total
- bulkitempromo: if q >= 20 for an item, then 10% of the item
- largeorderpromo: if type >= 10, then 7% of the total
We can implement such strategies as below in a traditional sense.
from abc import ABC, abstractmethod
from collections import namedtuple
Customer = namedtuple('Customer', 'name fidelity')
class LineItem:
def __init__(self, product, quantity, price):
self.product = product
self.quantity = quantity
self.price = price
def total(self):
return self.quantity * self.price
class Order:
def __init__(self, customer, cart, promotion=None):
self.customer = customer
self.cart = list(cart)
self.promotion = promotion
def total(self):
if not hasattr(self, '__total'):
self.__total = sum((item.total() for item in self.cart))
return self.__total
def due(self):
if self.promotion == None:
discount = 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):
@abstractmethod
def discount(self, order):
""" returns discount """
class FidelityPromo(Promotion):
def discount(self, order):
return order.total() * 0.05 if order.customer.fidelity >= 1000 else 0
class BulkItemPromo(Promotion):
def discount(self, order):
discount = 0
for item in order.cart:
if item.quantity >= 20:
discount += item.total() * 0.1
return discount
class LargeOrderPromo(Promotion):
def discount(self, order):
distinct_items = {item.product for item in order.cart}
return order.total() * 0.1 if len(distinct_items) >= 10 else 0
joe = Customer('John', 0)
ann = Customer('Ann', 1100)
cart = [LineItem('banana', 4, 0.5), LineItem('apple', 10, 1.5), LineItem('watermellon', 5, 5.0)]
print(Order(joe, cart, FidelityPromo()))
print(Order(ann, cart, FidelityPromo()))
print(Order(joe, cart, BulkItemPromo()))
Above code works fine. However, we can make it concise with python functions.
+ When initiating 'Order object', we use 'Promotion objects'. It means we have to create 'Promotion object' everytime we initiate 'order object', which is a waste. We can reuse 'Promotion object', but it will cost us a longer and complicated code.
from collections import namedtuple
Customer = namedtuple('Customer', 'name fidelity')
class LineItem:
def __init__(self, product, quantity, price):
self.product = product
self.quantity = quantity
self.price = price
def total(self):
return self.quantity * self.price
class Order:
def __init__(self, customer, cart, promotion=None):
self.customer = customer
self.cart = list(cart)
self.promotion = promotion
def total(self):
if not hasattr(self, '__total'):
self.__total = sum((item.total() for item in self.cart))
return self.__total
def due(self):
if self.promotion == None:
discount = 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):
return order.total() * 0.05 if order.customer.fidelity >= 1000 else 0
def bulk_item_promo(order):
discount = 0
for item in order.cart:
if item.quantity >= 20:
discount += item.total() * 0.1
return discount
def large_order_promo(order):
distinct_items = {item.product for item in order.cart}
return order.total() * 0.1 if len(distinct_items) >= 10 else 0
joe = Customer('John', 0)
ann = Customer('Ann', 1100)
cart = [LineItem('banana', 4, 0.5), LineItem('apple', 10, 1.5), LineItem('watermellon', 5, 5.0)]
print(Order(joe, cart, fidelity_promo))
print(Order(ann, cart, fidelity_promo))
print(Order(joe, cart, bulk_item_promo))
Above code is more concise and readable. On top of that, it is more flexible. Imagine if we should add a new function "best_promo" which returns the largest discount for an order. In the previous code, it would be much difficult. On the other hand, it can be implemented as simple as below.
def best_promo(order):
promos = [fidelity_promo, bulk_item_promo, large_order_promo]
return max((promo(order) for promo in promos))
joe = Customer('John', 0)
ann = Customer('Ann', 1100)
cart = [LineItem('banana', 4, 0.5), LineItem('apple', 10, 1.5), LineItem('watermellon', 5, 5.0)]
print(Order(joe, cart, best_promo))
print(Order(ann, cart, best_promo))
Another tip for the above code is that we can automatically update 'promos list' as below and by doing so it will less likely cause bugs when we implement additional promotion strategies.
def best_promo(order):
promos = [globals()[name] for name in globals() if name.endswith('_promo') and name != 'best_promo']
return max((promo(order) for promo in promos))
The same can be achieved with modules as below.
def best_promo(order):
import inspect
import promotions
promos = [func for name, func in inspect.getmembers(promotions, inspect.isfunction)]
return max((promo(order) for promo in promos))
Decorator may provide a nicer solution.
promos = []
def promotion(promo_func):
promos.append(promo_func)
return promo_func
@promotion
def fidelity_promo(order):
return order.total() * 0.05 if order.customer.fidelity >= 1000 else 0
@promotion
def bulk_item_promo(order):
discount = 0
for item in order.cart:
if item.quantity >= 20:
discount += item.total() * 0.1
return discount
@promotion
def large_order_promo(order):
distinct_items = {item.product for item in order.cart}
return order.total() * 0.1 if len(distinct_items) >= 10 else 0
def best_promo(order):
return max(promo(order) for promo in promos)
'Computer Science > Python' 카테고리의 다른 글
Python: Object References (0) | 2022.04.04 |
---|---|
Python: Decorator & Closure (0) | 2022.03.29 |
Python: Memoryview function (0) | 2022.03.23 |
Python Immutable Dictionary, Set (0) | 2022.03.22 |
Python Data Structures: Sequences (0) | 2022.03.21 |