继承:在 Python 中模拟非虚函数

Inheritance: emulating non-virtual functions in Python

我是 Python 的新手,我来自 C++,所以我怀疑我的思维方式被我先入为主的观念“污染”了。我将解释我正在尝试做什么以及我面临的问题,但请注意下面的代码是一个“人工”小示例,它会重现我的问题。

说在某些时候我有这种情况,其中 B 只覆盖 A.plot_and_clear() 因为这是我从 B:

需要的全部
class A:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def clear(self):
        print("clear A start")
        self.x = 0
        self.y = 0
        print("clear A end")

    def plot(self):
        print("plot A start")
        print(str(self.x))
        print(str(self.y))
        print("plot A end")

    def plot_and_clear(self):
        print("plot & clear A start")
        self.plot()
        self.clear()
        print("plot & clear A end")


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

    def plot_and_clear(self):
        print("plot & clear B BAD start")
        super().plot_and_clear()
        print(str(self.z))
        self.z = 0
        print("plot & clear B BAD end")

def main():
    myObject = B(1, 2, 3)
    myObject.plot_and_clea()

main()

在这种情况下,输出正是我所期望的,如下所示:

plot & clear B start
plot & clear A start
plot A start
1
2
plot A end
clear A start
clear A end
plot & clear A end
3
plot & clear B end

后来我意识到我还需要用 B.plot() 覆盖 A.plot(),其余的保持不变,如下所示:

class A:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def clear(self):
        print("clear A start")
        self.x = 0
        self.y = 0
        print("clear A end")

    def plot(self):
        print("plot A start")
        print(str(self.x))
        print(str(self.y))
        print("plot A end")

    def plot_and_clear(self):
        print("plot & clear A start")
        self.plot()
        self.clear()
        print("plot & clear A end")


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

    def plot(self):
        print("plot B start")
        super().plot()
        print(str(self.z))
        print("plot B end")

    def plot_and_clear(self):
        print("plot & clear B BAD start")
        super().plot_and_clear()
        print(str(self.z))
        self.z = 0
        print("plot & clear B BAD end")

def main():
    myObject = B(1, 2, 3)
    myObject.plot_and_clea()

main()

现在,如果我们 运行 同一个 main,输出会发生变化且错误,如下所示:

plot & clear B start
plot & clear A start
plot B start
plot A start
1
2
plot A end
3
plot B end
clear A start
clear A end
plot & clear A end
3
plot & clear B end

这是因为现在,来自 B.plot_and_clear() 的 super().plot_and_clear() 调用 B.plot() 而不是 A.plot ().这样做的结果是,通过简单地添加一个函数,我破坏了 B.plot_and_clear() 之前的良好行为,这至少可以说是令人费解的。

我理解这是由于 MRO 在 python 中的工作方式,看起来与 c++ 相比完全翻转了。现在,无论我们是否知道发生这种情况的确切原因,我仍然认为这种行为是不可取的,应该有一种方法来防止它发生,要么选择一些“安全”的代码结构,要么使用其他一些语言结构。

知道如何“围绕”或“沿用”语言的这一方面吗?

非常感谢。

__init__ 只能用于 初始化 现有对象。 (尽管对象的创建和对 __init__ 的调用通常都发生在对类型本身的调用中。)

使用专用的 class 方法作为替代构造函数(例如复制构造函数或从另一个对象构造一个对象)。例如,

class Object:
    def __init__(self, *, mass=0, **kwargs):
        super().__init__(**kwargs)
        self.mass_ = mass

    @classmethod
    def from_object(cls, obj, **kwargs):
        return cls(mass=obj.mass_, **kwargs)


class Vehicle(Object):
    def __init__(self, *, wheels=4, **kwargs):
        super().__init__(**kwargs)
        self.wheels_ = wheels
 
    @classmethod
    def from_vehicle(cls, vehicle, **kwargs):
        return cls(mass=vehicle.mass_, wheels=vehicle.wheels_, **kwargs)


o = Object(mass=100)
v1 = Vehicle.from_object(o)
v2 = Vehicle.from_vehicle(v2)

v3 = Vehicle.from_object(o, wheels=6)
v4 = Vehicle.from_vehicle(v3)

请参阅 https://rhettinger.wordpress.com/2011/05/26/super-considered-super/ 了解我们使用关键字参数的原因。

即使 from_object 本身不需要任何额外的关键字参数,我们也接受 cls(可以是 Object 或 class 的任何子class 的关键字参数=15=]) 可能期望。

还要注意,Vehicle 本身不必定义 from_objectVehicle.from_object 将改为使用 Object.from_object 来创建载具。这听起来可能很奇怪,但是 Object.from_object 的工作 不一定是 创建一个 Object,而是知道如何“解压”一个 [=15] 的实例=] 以创建 cls.

的实例

感谢上面评论中的冗长讨论,我想我已经掌握了在 python 中所有函数都是虚函数的概念,因此所描述的行为也正是人们在 C++ 中所期望的,当所有 base-class 方法都是虚拟的。

然而,如果可以选择将 base-class 函数声明为 non-virtual,这样就可以准确地控制基 class 方法在调用它时的行为方式在另一个 base-class 方法中,不管子 class 对他们的覆盖做了什么。

到目前为止,我发现了两种方法可以在某种程度上重现该功能或至少部分控制 base-class:

的行为

方法 1):当需要 non-virtual 行为时,在其他 base-class 方法中显式调用 base-class 方法:

class A:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def clear(self):
        print("clear A start")
        self.x = 0
        self.y = 0
        print("clear A end")

    def plot(self):
        print("plot A start")
        print(str(self.x))
        print(str(self.y))
        print("plot A end")

    def plot_and_clear(self):
        print("plot & clear A start")
        A.plot(self)
        A.clear(self)
        print("plot & clear A end")


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

    def plot(self):
        print("plot B start")
        super().plot()
        print(str(self.z))
        print("plot B end")

    def plot_and_clear(self):
        print("plot & clear B BAD start")
        super().plot_and_clear()
        print(str(self.z))
        self.z = 0
        print("plot & clear B BAD end")

def main():
    myObject = B(1, 2, 3)
    myObject.plot_and_clea()

main()

# Output as desired despite the presence of overrides:

# plot & clear B start
# plot & clear A start
# plot A start
# 1
# 2
# plot A end
# clear A start
# clear A end
# plot & clear A end
# 3
# plot & clear B end

方法2)Name-Mangle那些需要non-virtual的函数,内部只使用name-mangled版本:

class A:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def clear(self):
        print("clear A start")
        self.x = 0
        self.y = 0
        print("clear A end")
    __clear = clear

    def plot(self):
        print("plot A start")
        print(str(self.x))
        print(str(self.y))
        print("plot A end")
    __plot = plot


    def plot_and_clear(self):
        print("plot & clear A start")
        self.__plot()
        self.__clear()
        print("plot & clear A end")


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

    def plot(self):
        print("plot B start")
        super().plot()
        print(str(self.z))
        print("plot B end")

    def plot_and_clear(self):
        print("plot & clear B BAD start")
        super().plot_and_clear()
        print(str(self.z))
        self.z = 0
        print("plot & clear B BAD end")

def main():
    myObject = B(1, 2, 3)
    myObject.plot_and_clea()

main()

# Output as desired despite the presence of overrides:

# plot & clear B start
# plot & clear A start
# plot A start
# 1
# 2
# plot A end
# clear A start
# clear A end
# plot & clear A end
# 3
# plot & clear B end

我希望这对像我一样在从静态语言切换到动态语言时遇到困难的任何人有所帮助,尽管可能在某些时候我们应该更好地接受语言的核心哲学,而不是试图强行适应它我们的老办法:)