返回新 class 对象的正确方法(也可以扩展)

Correct way of returning new class object (which could also be extended)

我正在尝试找到一种在 class 方法中 returning 一个(新)class 对象的好方法,该方法也可以扩展。

我有一个 class (classA),它具有其他方法,其中 return 是一个新的 classA object after some processing

class classA:
   def __init__(): ...

   def methodX(self, **kwargs):
      process data
      return classA(new params)

现在,我将这个 class 扩展到另一个 classB。我需要 methodX 来做同样的事情,但是这次 return classB 而不是 classA

class classB(classA):
   def __init__(self, params):
      super().__init__(params)
      self.newParams = XYZ
   
   def methodX(self, **kwargs):
      ???

这可能是一件微不足道的事情,但我就是想不通。最后,我不想每次 class 扩展时都重写 methodX。

感谢您的宝贵时间。

像这样使用 __class__ 属性:

class A:
    def __init__(self, **kwargs):
        self.kwargs = kwargs

    def methodX(self, **kwargs):
        #do stuff with kwargs
        return self.__class__(**kwargs)

    def __repr__(self):
        return f'{self.__class__}({self.kwargs})'

class B(A):
    pass

a = A(foo='bar')
ax = a.methodX(gee='whiz')
b = B(yee='haw')
bx = b.methodX(cool='beans')

print(a)
print(ax)
print(b)
print(bx)
class classA:
    def __init__(self, x):
       self.x = x

    def createNew(self, y):
        t = type(self)
        return t(y)

class classB(classA):
    def __init__(self, params):
        super().__init__(params)


a = classA(1)
newA = a.createNew(2)

b = classB(1)
newB = b.createNew(2)

print(type(newB))
# <class '__main__.classB'>

我想提出我认为最干净的方法,尽管与现有答案相似。这个问题感觉很适合 class 方法:

class A:
    @classmethod
    def method_x(cls, **kwargs):
        return cls(<init params>)

使用 @classmethod 装饰器确保第一个输入(传统上命名为 cls)将引用方法所属的 Class,而不是实例。

(通常我们称第一个方法为input self this指的是该方法所属的实例)

因为 cls 指的是 A,而不是 A 的 实例,我们可以像调用 A().

但是,在 A 继承 的 class 中,cls 将改为引用 child class,按要求:

class A:
    def __init__(self, x):
        self.x = x
    @classmethod
    def make_new(cls, **kwargs):
        y = kwargs["y"]
        return cls(y) # returns A(y) here

class B(A):
    def __init__(self, x):
        super().__init__(x)
        self.z = 3 * x

inst = B(1).make_new(y=7)
print(inst.x, inst.z)

现在您可以期望打印语句产生 7 21.

inst.z 存在应该为您确认 make_new 调用(仅在 A 上定义并且未被 B 更改继承)确实创建了 B 的实例。


但是,有一点我必须指出。继承未更改的 make_new 方法仅有效,因为 B 上的 __init__ 方法与 A 上的方法具有相同的调用签名。如果不是这种情况,那么对 cls 的调用可能有必须改变。

这可以通过在 __init__ 方法上允许 **kwargs 并将泛型 **kwargs 传递到 parent class 中的 cls() 来规避:

class A:
    def __init__(self, **kwargs):
        self.x = kwargs["x"]
    @classmethod
    def make_new(cls, **kwargs):
        return cls(**kwargs)

class B(A):
    def __init__(self, x, w):
        super().__init__(x=x)
        self.w = w
        

inst = B(1,2).make_new(x="spam", w="spam")
print(inst.x, inst.w)

这里我们可以给 B 一个不同的(更严格!)签名。

这说明了一个一般原则,即 parent classes 通常比 children 更 abstract/less 具体。

由此可见,如果您想要两个 class 实质上共享行为但做完全不同的事情的 es,最好创建 three classes:一个相当抽象的定义 behaviour-in-common,两个 children 给你你想要的特定行为。