Выберите класс реализации в глубоких иерархиях - PullRequest
1 голос
/ 23 сентября 2019

Я создаю модуль Python с требованием, чтобы конечный пользователь этого модуля мог выбрать конкретную реализацию класса, которая будет использоваться различными другими классами в модуле (предпочтительно с реализацией по умолчанию).

Одним из ограничений является то, что этот класс встроен в глубокие иерархии.Вот минимальный пример, иллюстрирующий проблему:

class X():
    pass

class Y():
    pass

Z = X # or Z = Y

class A():
    def __init__(self):
        self.zoo = Z()

class B():
    def __init__(self):
        self.zoo = Z()

class C(B):
    pass

Для целей этого примера я использовал глобальную переменную Z, чтобы определить, какой класс будет выбран.Это действительно не идеально и требует от пользователя monkey patch / изменения глобальной переменной после импорта.Это также предотвращает смешивание различных реализаций в одном и том же проекте.

from my_module import A, B, C, Y, Z

Z=Y

class U():
    def __init__():
        self.foo = C()

Какой способ решения этой проблемы вы предпочитаете?Я хотел бы избежать передачи класса в качестве параметра конструктора, поскольку выбор, например, X или Y должен быть связан с классом, а не с экземпляром.Поскольку Python не поддерживает параметризованный импорт, я думал о декораторах и метаклассах, но не смог придумать чистый дизайн.

Ответы [ 2 ]

1 голос
/ 23 сентября 2019

Другим способом было бы сделать что-то вроде того, что wxPython делает для управления версиями .Это будет включать разделение вашего кода на три части: библиотека классов, реальная бизнес-логика и модуль выбора.

library.py

from abc import ABC

class Base(ABC):
    ...

class Impl1(Base):
    ...

class Impl2(Base):
    ...

selector.py

from typing import Type
import library 

impl= library.Impl1

def set_impl(cls: Type[library.Base]):
    global impl
    impl = cls

core.py

import library
import selector 

impl = selector.impl

class A():
    def __init__(self):
        self.zoo = impl()

Затем в коде, который использует ваш пакетВы просто должны быть уверены, что вызовете set_impl перед импортом core.py:

import library
import selector
selector.set_impl(library.Impl2)

import core
print(core.impl)  # <class 'library.Impl2'>

Это работает, потому что объект модуля, который создается для модуля selector, создается только один раз, а все остальныеМодуль делится этим.Поэтому, когда основной модуль изменяет selector, эти изменения видны для core

1 голос
/ 23 сентября 2019

Похоже, что ваши экземпляры C используют X или Y, но это так, решается пользователем класса C, которому также должно быть разрешено смешивать использование обоих.В таком случае экземпляры должны получить решение во время создания экземпляров, что означает в качестве аргумента конструктора:

class X():
    pass

class Y():
    pass

class A():
    def __init__(self, x_or_y):
        # you will find a more fitting name in your
        # concrete case than x_or_y ;-)
        self.x_or_y = x_or_y
        self.zoo = x_or_y()

class B():
    def __init__(self, x_or_y):
        self.x_or_y = x_or_y
        self.zoo = x_or_y()

class C(B):
    pass

Создание экземпляра затем выполняется следующим образом:

from my_module import A, B, C, Y

class U():
    def __init__():
        self.foo = C(Y)
...