【问题标题】:Initializing an object in a parent class with the type of the child用子类的类型初始化父类中的对象
【发布时间】:2021-02-11 16:00:28
【问题描述】:

我编写了一个父类,在其中定义了所有子类都应具有的一些函数(示例 __mul__、__truediv__ 等)。这些函数在执行后应该保持子类的类型。

这里有一些代码来解释我的意思:

class Magnet():
    
    def __init__(self, length, strength):
        
        self.length = length
        self.strength = strength
        
        return
    
    def __mul__(self, other):

        if np.isscalar(other):
            return Magnet(self.length, self.strength * other)

        else:
            return NotImplemented
        
class Quadrupole(Magnet):
    
    def __init__(self, length, strength, name):
        
        super().__init__(length, strength)
        
        self.name = name
        
        return

现在如果我这样做:

Quad1 = Quadrupole(2, 10, 'Q1')
Quad2 = Quad1 * 2

那么 Quad1 属于“__main__.Quadrupole”类型,Quad2 属于“__main__.Magnet”类型。

我想知道如何做到这一点,以便保留子类型并且不会将其重新转换为父类型。一种解决方案是在子类中重新定义这些函数并更改

        if np.isscalar(other):
            return Magnet(self.length, self.strength * other)

        if np.isscalar(other):
            return Quadrupole(self.length, self.strength * other)

但进行继承的主要原因是不复制粘贴代码。可能类似于 super() 但向下,或者可能是类类型的占位符...

感谢您的帮助。

采用的解决方案

使用

return type(self)(self.length, self.strength * other)

有魅力。它会引发错误,因为我忘记在 Magnet.__init__() 中添加“名称”参数(我的原始代码确实如此,但在简化示例时搞砸了)。

我在这里也发现了同样的问题:Returning object of same subclass in __add__ operator

【问题讨论】:

    标签: python-3.x inheritance types super


    【解决方案1】:

    解决方案 1

    您可以使用type(self) 获取类型并从中创建一个新对象。

    def __mul__(self, other):
        if np.isscalar(other):
            return type(self)(self.length, self.strength * other)
        raise NotImplemented
    

    (也提出了NotImplemented而不是返回它。)

    现在使用您的代码将导致:

        return type(self)(self.length, self.strength * other)
    TypeError: __init__() missing 1 required positional argument: 'name'
    

    这需要Quadrupolename 的默认参数。

    class Quadrupole(Magnet):
        def __init__(self, length, strength, name='unknown'):
            super().__init__(length, strength)
            self.name = name
    

    你的代码很开心,但你可能不开心。原因是您现在丢失了有关 Quadrupole 类的 name 的信息。

    解决方案 2

    您正在返回该类的新实例,有时这不是必需的,您可以改变旧类。这会将您的代码简化为:

    def __mul__(self, other):
        if np.isscalar(other):
            self.strength *= other.strength
            return self
        raise NotImplemented
    

    这会改变你的旧实例。

    解决方案 3

    解决方案 1 的主要问题是您会丢失信息,因为您正在创建一个新类。现在一个可能的选择是只复制那个类。不幸的是,复制一个类并不总是那么简单。

    基于this SO question,在这种情况下使用deepcopy 可能会起作用,但如果你有一个复杂的类结构,你可能必须实现__copy__ 才能得到你想要的。

    def __mul__(self, other):
        if np.isscalar(other):
            class_copy = deepcopy(self)
            class_copy.strength *= other
            return class_copy
        raise NotImplemented
    

    您可以选择提供__copy__ 方法。对于提供的代码 sn-p 这不是必需的,但在更复杂的情况下可能是必需的。

    def __copy__(self):
        return Quadrupole(self.length, self.strength, self.name)
    

    【讨论】:

    • 很抱歉,我试图在这里展示一个简单的案例,弄乱了这个名字。在我的真实情况下,父磁铁也有一个名字。我将保持原样,因为您解决了另一个可能的问题。我很喜欢return type(self)(self.length, self.strength * other),也发现了self.__class__()。对于 python 3.x,我找不到两者之间的大区别。
    • 关于NotImplemented:我在这里(stackoverflow.com/questions/878943/>)发现returnraise 更好。至少据我所知。
    • @Felipe,我在那里学到了一些新东西,我通常总是提出NotImplementedError。关于type__class__ 之间的区别,我认为this 的帖子可能会有所帮助。
    • 感谢您的链接。我想可能是总是使用全能的type(),因为 __class__ 很容易被覆盖。
    猜你喜欢
    • 2021-04-24
    • 2023-02-02
    • 1970-01-01
    • 1970-01-01
    • 2023-03-16
    • 1970-01-01
    • 1970-01-01
    • 2012-10-31
    • 2016-06-13
    相关资源
    最近更新 更多