python - Strategy pattern in python
问题描述
I am coming from C# background and in order to implement a strategy pattern, we would always use an interface, for example: ILoggger. Now as I understand, in duck-typed languages such as Python, we can avoid this base class/contract.
My question is, is this the best way to implement a strategy pattern by taking advantage of duck typing? And, does this duck typing way of doing this make it clear to the next user of my code that this is an "point of extension"? Also, I think it is better to use type hints to help the next person looking at your code to see what the type of the strategy should be, but with duck-typing without base class/contract, which type do you use? One of the already concrete classes?
Here is some code:
class FileSystemLogger():
def log(self, msg):
pass
class ElasticSearchLogger():
def log(self, msg):
pass
# if i wanted to use type hints, which type should logger be here?
class ComponentThatNeedsLogger():
def __init__(self, logger):
self._logger = logger
# should it be this?
class ComponentThatNeedsLogger():
def __init__(self, logger : FileSystemLogger):
self._logger = logger
Could someone please advise what is the most standard/Pythonic/readable way to handle this?
I am not looking for the "here is answer in 2 lines of code" answer.
解决方案
If you really wanted to go classes all the way and enforce your base class usage create an ABC: abstract base class / method and some implementations of it:
Attributation: used Alex Vasses answer here for lookup purposes
from abc import ABC, abstractmethod
class BaseLogger(ABC):
""" Base class specifying one abstractmethod log - tbd by subclasses."""
@abstractmethod
def log(self, message):
pass
class ConsoleLogger(BaseLogger):
""" Console logger implementation."""
def log(self, message):
print(message)
class FileLogger(BaseLogger):
""" Appending FileLogger (date based file names) implementation."""
def __init__(self):
import datetime
self.fn = datetime.datetime.now().strftime("%Y_%m_%d.log")
def log(self,message):
with open(self.fn,"a") as f:
f.write(f"file: {message}\n")
class NotALogger():
""" Not a logger implementation."""
pass
Then use them:
# import typing # for other type things
class DoIt:
def __init__(self, logger: BaseLogger):
# enforce usage of BaseLogger implementation
if isinstance(logger, BaseLogger):
self.logger = logger
else:
raise ValueError("logger needs to inherit from " + BaseLogger.__name__)
def log(self, message):
# use the assigned logger
self.logger.log(message)
# provide different logger classes
d1 = DoIt(ConsoleLogger())
d2 = DoIt(FileLogger())
for k in range(5):
d1.log(str(k))
d2.log(str(k))
with open(d2.logger.fn) as f:
print(f.read())
try:
d3 = DoIt( NotALogger())
except Exception as e:
print(e)
Output:
0
1
2
3
4
file: 0
file: 1
file: 2
file: 3
file: 4
logger needs to inherit from BaseLogger
As a sidenote: python already has quite sophisticated abilities to log. Look into Logging if that is the sole purpose of your inquiry.
推荐阅读
- rust - 为什么编译器让我在一个盒子里借另一个借?
- elasticsearch - 弹性搜索使用范围查询查找字段中的差异
- angular-material - 如何阻止占位符在 Angular 8 mat select 中作为标签浮动
- python - 熊猫 fpr python
- amazon-web-services - AWS:如何使用 Cloudformation 在 API Gateway 中启用 CORS?
- php - 在 Laravel 中将 HTTP 代码返回到 POST 表单
- python - addwstr() 返回 ERR
- python - 如何在 DataFrame 中创建一个显示最后记录的峰值的列?
- php - 方法帖子或会话
- c - 检查 SSH 私钥是否已加密