Python: Extend Class / 从使用工厂方法的第三方包返回的对象

Python: Extend Class / Object returned from a third party package that uses factory method

我正在尝试确定将第三方 class 包装在另一个 class 中的最佳方法,以便我可以提供一致的内部 API。第三方 class 是使用相当复杂的工厂方法创建的,因此永远不会直接实例化。被包装的其他 classes 是直接创建的。

在正常情况下,我会简单地继承 Class,然后根据需要进行扩展,即

class MyCustomClassWithStandardAPI(StandardThirdPartyClass):
    def custom_method_that_defines_api(self):
        return super().third_party_method_being_wrapped()

但在这种情况下,因为对象需要通过第三方工厂方法初始化,我无法包装 class,除了复制工厂方法本身(它很长,所以会如果将来工厂发生变化是有风险的)。

class MyCustomClassWithStandardAPI(ThirdPartyClassThatIsCreatedByFactoryMethod):
    def custom_method_that_defines_api(self):
        ....

def complicated_third_party_factory_method():
    ...
    ...
    returns ThirdPartyClassThatIsCreatedByFactoryMethod(a, b, c, d, ...)

因为Class是工厂方法创建的,所以我永远无法打断第三方class的实例化,换成自己的子class.

关于如何在 Python 中实现这一点有什么想法吗?

如果另一个 class 的实例创建过程超出了正常的协作继承,甚至选择要实例化的 classes,那么你最好的方法肯定不会是继承.

您最好在另一个层次结构中创建与该对象的一个​​实例的关联,并根据您的需要让自己保持干净和统一的界面。

即:


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 方库有很多方法和属性,其中大部分你 不需要自定义,也不需要一一写下来—— 您可以在包装器 class 上自定义属性访问以遵从包装器 attribute/method直接用简单的__getattr__方法。在上面的 class 中添加:

    def __getattr__(self, attr):
        return getattr(self.api, attr)

没有任何继承。

如果你需要继承...

有时您需要您的代理对象 "be" 其他类型的对象,例如将您自己的实例传递给方法或其他库提供的 classes 时。 然后,您可以通过在您这边使用更简单的工厂方法动态创建 class 来强制动态继承,而无需干预另一方的工厂方法:


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

(如果许多这样的实例将在长期进程中创建,那么您应该缓存动态创建的 classes - 更简单的方法是将 class 工厂推迟到偶数将父 class 作为参数并使用 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

猴子补丁

上面的例子都是关于 "your side only" 的故事 - 但根据你使用的方式和其他图书馆的性质, "monkey patch" 它可能更容易 - 那是:在 3rdy 方工厂方法的命名空间中,替换它打算由派生的 classes 使用的基础 classes - 这样将使用派生的 classes由图书馆的工厂。

Python 中的 Monkey 修补只是分配对象的问题 - 但标准库有一个 unittest.mock.patch 可调用函数,它提供的实用程序不仅可以执行修补,还可以进行清理-up,恢复原始值,补丁使用结束后。

如果您的库工厂本身不使用任何 Base classes,而是在工厂体内构建所有 class,则不能使用此方法。 (请记住,由于这不是正确的 "collaborative" 做法,库的作者可以在任何版本中自由更改设计的这一部分)。

但本质上:



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(...)

我会在这里使用 duck typing,因为 Python 很容易。隐藏原理就是你自己的class只是包含了一个需要的复杂class的子对象,把它不知道的所有请求都透明地传递给那个对象

唯一的问题是某些代码使用 isinstanceissubclass,它会中断。

如果这不是必需的,getattr 就足够了:

class MyCustomClassWithStandardAPI:

    def __init__(self, thirdParty):             # store the "inherited" object at initialization time
        self.inner = thirdParty

    # define additional methods
    def custom_method_that_defines_api(self):
        ....

    # delegate everything else to the "inherited" object
    def __getattr__(self, attr):
        return getattr(self.inner, attr)

你这样使用它:

obj = MyCustomClassWithStandardAPI(complicated_third_party_factory_method())
obj.custom_method_that_defines_api()     # Ok, uses the specialized method
obj.third_party_method()                 # Ok delegates to the inner object