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的子对象,把它不知道的所有请求都透明地传递给那个对象
唯一的问题是某些代码使用 isinstance
或 issubclass
,它会中断。
如果这不是必需的,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
我正在尝试确定将第三方 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的子对象,把它不知道的所有请求都透明地传递给那个对象
唯一的问题是某些代码使用 isinstance
或 issubclass
,它会中断。
如果这不是必需的,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