Шаблон стратегии в питоне - PullRequest
1 голос
/ 08 июня 2019

Я пришел из C # фона, и для реализации шаблона стратегии мы всегда будем использовать интерфейс, например: ILoggger.Теперь, насколько я понимаю, в языках типа утка, таких как Python, мы можем избежать этого базового класса / контракта.

Мой вопрос заключается в том, является ли это наилучшим способом реализации шаблона стратегии, используя преимущества утки?И позволяет ли этот способ печатания утки понять следующему пользователю моего кода, что это «точка расширения»?Кроме того, я думаю, что лучше использовать подсказки типов, чтобы помочь следующему человеку, смотрящему на ваш код, увидеть, каким должен быть тип стратегии, но с типизацией утки без базового класса / контракта, какой тип вы используете?Один из уже конкретных классов?

Вот код:

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

Может кто-нибудь посоветовать, какой самый стандартный / Pythonic / читаемый способ справиться с этим?

Я не ищу ответ "вот ответ в 2 строках кода".

Ответы [ 3 ]

2 голосов
/ 08 июня 2019

Если вы действительно хотите пройти классы до конца и обеспечить использование базового класса, создайте ABC: абстрактный базовый класс / метод и некоторые его реализации:

Атрибуция: используется Алекс Вассес ответ здесь для поиска

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

Тогда используйте их:

# 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)

Выход:

0
1
2
3
4 
file: 0
file: 1
file: 2
file: 3
file: 4

logger needs to inherit from BaseLogger

Как примечание: python уже имеет достаточно сложные возможности для входа. Посмотрите на Регистрация , если это единственная цель вашего запроса.

1 голос
/ 08 июня 2019

Насколько я знаю, наиболее распространенный способ реализации шаблона стратегии в Python - это передача функции (или вызываемой функции). Функции - это объекты первого класса в Python, поэтому, если все, что нужно потребителю - это функция, вам не нужно давать больше. Конечно, вы можете комментировать, если хотите. Предполагая, что вы хотите записывать только строки:

class ComponentThatNeedsLogger:
    def __init__(self, log_func: Callable[[str], None]):
        self._log_func = log_func

Это позволяет вам создать простой регистратор на лету:

ComponentThatNeedsLogger(
    log_func=print
)

Но вы также можете использовать все возможности классов для создания сложного регистратора и передачи только соответствующего метода.

class ComplexLogger:
    def __init__(self, lots_of_dependencies):
        # initialize the logger here

    def log(self, msg: str): None
        # call private methods and the dependencies as you need.

    def _private_method(self, whatever):
        # as many as you need.

ComponentThatNeedsLogger(
    log_func= ComplexLogger(lots_of_dependencies).log
)
1 голос
/ 08 июня 2019

В Python, как правило, нет необходимости в полномасштабном шаблоне стратегии с принудительным применением типов во время компиляции благодаря безопасности во время выполнения и изящному завершению.

Если вы хотите настроить некоторые части существующего кода, общая практика:

  • заменить соответствующий метод - будь то путем подкласса, в т.ч. с добавлением или просто присваиванием (метод является атрибутом живого объекта, как и любой другой, и может быть переназначен точно так же; замена может даже быть не функцией, а объектом с __call__);
    • обратите внимание, что в Python вы можете создавать объекты, содержащие код (который включает в себя функции и классы) на лету, помещая его определение в функцию. Затем определение оценивается по мере выполнения включающей функции, и вы можете использовать доступные переменные (или закрытие) в качестве специальных параметров; или
  • принять обратный вызов в некоторый момент (или объект, методы которого вы будете вызывать в соответствующие моменты, эффективно выступая в качестве набора обратных вызовов); или
  • принимает строковый параметр, который является константой из определенного набора, который код затем проверяет в if / else или ищет в каком-либо реестре (будь то глобальный для модуля или локальный для класса или объекта);
    • существует enum с версии 3.4, но для простых случаев это считается слишком большим количеством недостатков для преимуществ (не читается при отладке и требует шаблонного кода), так как Python больше относится к гибкости по сравнению с C # в гибкости-против- шкала масштабируемости.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...