最近在把之前做数据分析的代码用类重写以增强可读性和复用性,于是遇到了python的这个“特性”,因此辑录如下。

python的类没有设计类似于cpp等语言中有的私有属性和私有方法,但是传统上会通过加下划线的方式来标识私有属性和方法,例如:

class foo
    def __init__(self):
        self._a = 0

    def __test(self, b):
        pass

这里,_afoo的对象的私有属性,__test是私有方法。那么,为了继承的时候父类的方法名和子类的方法名不至于互相冲突,又或者简单支持把方法定义为私有的这么一个功能,python会将上述私有方法重命名为__foo_test。这就是python的名称修饰(name mangling)。

和上一篇技术笔记一样,这是很简单的一个点。但是这个点却坑了我一次。简单的说,当时写了这么一段代码:

from  abc import ABC, abstractmethod

class foo(ABC):

    @abstractmethod
    def __test(self)
        pass

    def test_1(self):
        self.test()


class foo1(foo):

    def __test(self):
        print("child")


a = foo1()
a.test_1()

这么写的逻辑是,在父类中定义一个抽象的接口而在子类中实现。背景是,我需要设计一个类以计算数据在空间中的分布,针对每种不同物理量可能需要有不同计算得出空间中一点的数据的方法,而生成数据分布的方法是同样的。因此只需要在父类中实现一个生成空间分布的方法和计算一点的数据的通用方法,而在有需要时在子类实现一个计算一点数据的方法覆盖即可。

使用时发现报错:

Can't instantiate abstract class foo1 with abstract method __foo__test

这就很令人疑惑了(假定还不知道name mangling),首先,报错中的抽象方法哪里来的?其次,既然我已经在子类中实现了抽象方法,为什么还会提示有没有实现的抽象方法呢?

原来,在父类中定义的私有抽象方法,由于name mangling的机制,实际上是叫__foo_test,在子类中实现的实际上则是__foo1_test,那么自然会报错。可以用以下代码验证这个机制:

class foo(ABC):

    def __test(self)
        print("parent")

    def test_1(self):
        self.test()


class foo1(foo):

    def __test(self):
        print("child")


a = foo1()
a.test_1()

那么显然,上面的输出应该是parent

接下来是一点个人感受,非常主观。

实际上我觉得这并不是一个很好的机制。就拿上面的例子举例,调虽然test_1是在父类中实现的,但是却由子类的对象所继承而调用的,因此这个方法应算作是子类的方法。既然是子类的方法,而__test又在子类中有实现,那么应该优先调用的是子类的方法,用特殊覆盖一般。但由于这个机制的存在,实际上在父类中写的__test已经变成了__foo_test,反而导致要想调用子类的方法得额外添加代码,例如__getattr之类的方法。

因此,虽然可以理解这么设计的原因,但确实不是很喜欢。