User:Xyy23330121/Python/类的私有量与类的继承

来自维基学院


为篇幅起见,另起一个页面讲这些内容。

子类型与继承[编辑 | 编辑源代码]

类的一个重要方法是继承。我们可以这样定义一个类型的子类型:

class C: pass

class D(C): pass

此时, D 会继承来自 C 的一切属性或方法。这种单重继承的方式和定义函数时、变量的作用域很相似的。我们可以简单理解为:DC 的子类型时,D 的作用域也是 C 作用域的下级作用域。

多重继承(选学)[编辑 | 编辑源代码]

class C: pass

class D: pass

class E(C,D): pass

通过以上方法,可以让 E 同时作为 CD 的子类型。

子类继承父类型方法的顺序是按照其mro()方法所示的顺序的,读者可以通过输出 ClassName.mro() 的结果,得知 Python 会按照什么顺序去寻找方法或属性。

以上述例子为例,E.mro() 的输出为:

<class '__main__.E'>, <class '__main__.C'>, <class '__main__.D'>, <class 'object'>

即,它会先从类型 E 中查找方法。找不到时,再查找类型 C。若还找不到,则查找类型 D。最后查找类型 object。

继承顺序错误[编辑 | 编辑源代码]

如果这个顺序无法生成,则新的子类无法正常创建,并且会报错。比如:

class RootA: pass
class RootB: pass
class C(RootA, RootB): pass
class D(RootB, RootA): pass
class E(C,D): pass #报错:TypeError: Cannot create a consistent method resolution order (MRO) for bases RootA, RootB

读者在完全了解这些顺序之前,应当不要随意使用多重继承。具体顺序,参见此文档

私有量[编辑 | 编辑源代码]

大多数 Python 代码遵循一个规律:以下划线开头的名称应该被当作是私有部分,不应从外面随意更改——如果进行更改,可能会破坏一部分功能。这个思想在“类”上的体现是一个“名称改写”的功能。

class C:
    __a = 0
    def prt(self):
        print(self.__a)
#print(C.__a) #报错:AttributeError: type object 'C' has no attribute '__a'
print(C._C__a)#输出:0
C.prt(C)      #输出:0

具体来讲,在类型 ClassName 中使用以两个下划线开头的函数或属性时,Python 会自动把属性名中的 __ 改为 _ClassName__。而在类型外时,则不会进行这样的改写。

在 Python 中,任何变量的数值都是可以改写的。

property[编辑 | 编辑源代码]

class property(fget=None, fset=None, fdel=None, doc=None)

property 是一个特殊的、用作类型属性的类型。对 property 实例进行的读取、赋值和更改操作,会被分别转接到fget、fset和fdel函数上并执行。比如以下示例:

class C:
    def __init__(self): self.__x = 0
    def getx(self): return self.__x
    def setx(self, value): self.__x = value
    def delx(self): del self.__x
    x = property(getx, setx, delx, "I'm the 'x' property.")


c = C()
print(c.x)    #输出:0
c.x = 1
print(c._C__x) #输出:1
del c.x
try: print(c._C__x)
except: print("c._C__x deleted")
#输出:c._C__x deleted

在实际使用时,还可以使用装饰器来写出效果完全相同的、类的属性:

class C:
    def __init__(self): self.__x = 0
    
    @property
    def x(self): return self.__x
    
    @x.setter
    def x(self, value): self.__x = value
    
    @x.deleter
    def x(self): del self.__x

我们可以利用 property,来达成类似“只读属性”的效果,比如:

class ReadOnly:
    def __init__(self, value):
        self.__value = value
    @property
    def x(s): return s.__value
    @x.setter
    def x(s,v): raise AttributeError("readonly attribute")
    @x.deleter
    def x(s): raise AttributeError("readonly attribute")

a = ReadOnly(0)
print(a.x) #输出:0
a.x = 1    #报错:AttributeError: readonly attribute

需要注意的是,self.__value 本身是可以修改的,因此这个属性也并非真正的只读属性。

isinstance / issubclass[编辑 | 编辑源代码]

isinstance(object, classinfo)[编辑 | 编辑源代码]

classinfo 应输入类型、多个类型组成的元组或联合类型

如果 objectclassinfo 中某个类型的实例,或某个类型的、子类型的实例。则返回 True,反之,返回 False

issubclass(class, classinfo)[编辑 | 编辑源代码]

isinstanceclassinfo 应输入类型、多个类型组成的元组或联合类型

如果 classclassinfo 中某个类型的子类型,则返回 True,反之,返回 False

抽象基类[编辑 | 编辑源代码]

抽象基类是用于判断某个自定义类是否包含对应方法的。比如 collections.abc 模块中的 collection.abc.Sized。当一个类,其实例具有 __len__ 方法时,该类就会被视为是 collection.abc.Sized 的一个子类。这和创建该类时,是否显式地说明该类继承于 collection.abc.Sized 无关。

这个子类关系,是可以通过上述 isinstanceissubclass 函数检测出来的。这在一些情况下很有用。