Если другой класс выйдет из нормального совместного наследования для процесса создания его экземпляров и даже для выбора классов, для которых будет создан экземпляр, тогда вашим лучшим подходом, безусловно, не будет наследование.
Вы будете лучше создавая ассоциацию с одним экземпляром объекта в этой другой иерархии, и поддерживайте чистый и унифицированный интерфейс в соответствии с вашими потребностями.
То есть:
class MyCustomBase:
def __init__(self, [enough parameters to instantiate the 3rdy party thing you need]):
self.api = third_party_factory(parameters)
...
...
def stantard_comunicate(self, data):
if isinstance(self, api, TypeOne):
data = transform_data_for_type_one(data)
self.api.comunicate_one(data)
elif isinstance(self, api, TypeTwo)
data = transform_data_for_type_two(data)
self.api.comunicate_two(data)
...
Если в 3-й партии lib есть много методов и атрибутов, большинство из которых вам не нужно настраивать, вам не нужно записывать, а затем записывать либо один за другим - вы можете настроить доступ к атрибуту в своем классе-обертке, чтобы напрямую переходить к атрибуту / методу с помощью простого метода __getattr__
. Внутри вашего класса выше добавьте:
def __getattr__(self, attr):
return getattr(self.api, attr)
Без какого-либо наследования.
Если вам нужно наследование ...
Иногда вам понадобится ваш прокси-объект, чтобы "быть" из другой вид, например, при передаче ваших собственных экземпляров в методы или классы, предоставляемые другой библиотекой. Затем вы можете форсировать динамическое наследование c, не вмешиваясь в метод фабрики другой стороны, динамически создавая класс с гораздо более простым фабричным методом на вашей стороне:
def myfactory(...):
instance = thirdy_party_factory(...)
class MyClass(type(instance)):
def __init__(self, ...):
super().__init__(...)
self.my_init_code()
def my_init_code(self):
# whatver you need to initialize the attributes
# you use go here
...
# other methods you may want to customize can make use
# of "super()" normally
# Convert "other_instance" to be of type "my_instance":
instance.__class__ = MyClass
return instance
(Если в таком случае будет создано много таких экземпляров в долгоживущий процесс, тогда вы должны кэшировать динамически созданные классы. Более простой способ - просто перенести фабрику классов на еще более простой фабричный метод, который принимает родительский класс в качестве параметра, и использовать встроенный в Python lru_cache. «за это»:
from functools import lru_cache
@lru_cache()
der inner_factory(Base):
class MyClass(type(instance)):
def __init__(self, ...):
super().__init__(...)
self.my_init_code()
def my_init_code(self):
# whatver you need to initialize the attributes
# you use go here
...
# other methods you may want to customize can make use
# of "super()" normally
return MyClass
def myfactory(...):
instance = thirdy_party_factory(...)
MyClass = inner_factory(type(instance))
# Convert "other_instance" to be of type "my_instance":
instance.__class__ = MyClass
return instance
Patching Monkey
Приведенные выше примеры касаются «только вашей стороны» истории - но в зависимости от того, как вы используете вещи и от характера другой библиотеке, возможно, будет проще «обезьянить патч», то есть: заменить в пространстве имен метода фабрики 3-й стороны базовые классы, которые он намеревается использовать вашими производными классами, так, чтобы ваши отобранные классы были используется фабрикой библиотеки.
Исправление обезьян в Python это просто вопрос назначения объектов - но в стандартной библиотеке есть вызываемый элемент unittest.mock.patch
, который предлагает утилиту, которая не только выполняет исправление, но и заботится об очистке, восстанавливая исходные значения после окончания использования исправления.
Если фабрика вашей библиотеки сама не использует какие-либо базовые классы и строит все классы внутри тела фабрики, этот подход не может быть использован. (и имейте в виду, что, поскольку это не совсем «совместная» практика, авторы библиотеки могут изменять эту часть дизайна в любом выпуске).
Но по существу:
from unittest.mock import patch
from thirdparty.bases import ThirdyBase
from thirdyparty.factories import factory
class MyStandardBase(ThirdyBase):
def implement_standard_api(self, ...):
...
def mycode():
# Bellow, the last item on the dotted path is the name
# of the base class _inside_ the module that defines the
# factory function.
with patch("thirdyparty.factories.ThirdyBase", MyStandardBase):
myinstance = factory(...)