如何为子类编写工厂函数?

3
假设有一个类 A 和一个工厂函数 make_A

class A():
...


def make_A(*args, **kwars):
# returns an object of type A

这两个名称都定义在some_package中。

同时假设我想通过子类化来扩展A的功能,但又不想重写构造函数:

from some_package import A, make_A

class B(A):

    def extra_method(self, ...):
    # adds extra functionality 

我还需要编写一个新的工厂函数make_B来创建子类B

到目前为止,我找到的解决方案是

def make_B(*args, **kwargs):
    """
    same as make_A except that it returns an object of type B
    """
    out = make_A(*args, **kwargs)
    out.__class__ = B
    return out

这个方法看起来可以工作,但我有一些担心,因为直接修改__class__属性感觉像是一种hack。我也担心这种修改可能会导致意想不到的副作用。这是推荐的解决方案吗?还是有更"清晰"的模式来达到相同的结果?


也许你可以给 B 添加一个构造函数,它接受一个对象 A,然后只需 out = B(make_A(*args, **kwargs)) - Alexey Larionov
从您分享的代码中看来,B.__init__ 函数是从 A 继承而来的,并且 B 没有覆盖它,因此您可以简单地修改 make 函数以返回初始化参数而不是实例本身。 - Miquel Escobar
@MiquelEscobar。由于make_A是由另一个包提供的,并且其实现在将来可能会更改,因此我不认为这是最好的选项。 - zap
你可以在 B 中添加构造函数吗? - Alexey Larionov
@AlexeyLarionov 我可以向 B 添加一个构造函数,但我不想覆盖 __init__,因为我希望保持 B 的接口尽可能接近 A - zap
显示剩余2条评论
2个回答

1
我想我终于找到了一些不冗长但仍然有效的东西。为此,您需要用组合替换继承,这将允许通过执行self.a = ...来使用对象A
要模仿A的方法,可以使用__getattr__重载将这些方法(和字段)委派给self.a 下面的代码片段对我有效。
class A:
   def __init__(self, val):
      self.val = val
   def method(self):
      print(f"A={self.val}")

def make_A():
   return A(42)

class B:
    def __init__(self, *args, consume_A = None, **kwargs):
        if consume_A is None:
            self.a = A(*args, **kwargs)
        else:
            self.a = consume_A

    def __getattr__(self, name):
        return getattr(self.a, name)

    def my_extension(self):
        print(f"B={self.val * 100}")

def make_B(*args, **kwargs):
    return B(consume_A=make_A(*args, **kwargs))

b = make_B()
b.method() # A=42
b.my_extension() # B=4200

这种方法比你的方法更优越的原因在于修改__class__可能不是无害的。另一方面,__getattr____getattribute__是专门提供用于解决对象属性搜索的机制。有关详细信息,请参见本教程

谢谢您的回答!您可以加上一个简短的评论,解释一下采用这种方法相比在make_B中修改返回对象的__class__属性有什么优劣之处吗? - zap
1
因为我不确定修改__class__是否无害。另一方面,__getattr____getattribute__是专门提供的机制(教程)用于解决对象中属性搜索的问题。 - Alexey Larionov
谢谢,再次感谢。我擅自编辑了您的答案,包括您最后的评论。如果您不同意,请随时删除它。 - zap

0
通过接受类作为参数,使您的原始工厂函数更加通用:请记住,在Python中,甚至类也是对象。
def make(class_type, *args, **kwargs):
    return class_type(*args, **kwargs)


a = make(A)
b = make(B)

由于B具有与A相同的参数,因此您无需创建一个A然后将其转换为B:B继承自A,因此它“是 A”,并且将具有相同的功能,以及您添加的额外方法。


1
我认为需要调用来自外部库的 make_A - Alexey Larionov
这假设make_A函数的参数直接传递给类构造函数。我想要的是一个更通用的工厂函数,例如可以从一个或多个文件中读取一些数据,然后构造对象。 - zap
你可以创建一个 make_A() 函数,它在内部使用 make(),并将所有应该在 make_Amake_B 中运行的共享功能放在一个单独的函数中,然后在这两个函数中调用它。 - theberzi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接