上一篇介绍了Python中类相关的一些基本点,本文看看Python中类的继承和__slots__属性。
在Python中,同时支持单继承与多继承,一般语法如下:
class SubClassName(ParentClass1 [, ParentClass2, ...]): class_suite
实现继承之后,子类将继承父类的属性,也可以使用内建函数insubclass()来判断一个类是不是另一个类的子孙类:
class Parent(object): ''' parent class ''' numList = [] def numAdd(self, a, b): return a+b class Child(Parent): pass c = Child() # subclass will inherit attributes from parent class Child.numList.extend(range(10)) print Child.numList print "2 + 5 =", c.numAdd(2, 5) # built-in function issubclass() print issubclass(Child, Parent) print issubclass(Child, object) # __bases__ can show all the parent classes print Child.__bases__ # doc string will not be inherited print Parent.__doc__ print Child.__doc__
代码的输出为,例子中唯一特别的地方是文档字符串。 文档字符串对于类,函数/方法,以及模块来说是唯一的,也就是说__doc__属性是不能从父类中继承来的。
当在Python中出现继承的情况时,一定要注意初始化函数__init__的行为。
1. 如果子类没有定义自己的初始化函数,父类的初始化函数会被默认调用;但是如果要实例化子类的对象,则只能传入父类的初始化函数对应的参数,否则会出错。
class Parent(object): def __init__(self, data): self.data = data print "create an instance of:", self.__class__.__name__ print "data attribute is:", self.data class Child(Parent): pass c = Child("init Child") print c = Child()
代码的输出为:
2. 如果子类定义了自己的初始化函数,而没有显示调用父类的初始化函数,则父类的属性不会被初始化
class Parent(object): def __init__(self, data): self.data = data print "create an instance of:", self.__class__.__name__ print "data attribute is:", self.data class Child(Parent): def __init__(self): print "call __init__ from Child class" c = Child() print c.data
代码的输出为:
3. 如果子类定义了自己的初始化函数,显示调用父类,子类和父类的属性都会被初始化
class Parent(object): def __init__(self, data): self.data = data print "create an instance of:", self.__class__.__name__ print "data attribute is:", self.data class Child(Parent): def __init__(self): print "call __init__ from Child class" super(Child, self).__init__("data from Child") c = Child() print c.data
代码的输出为:
前面一个例子中,已经看到了通过super来调用父类__init__方法的例子,下面看看super的使用。
在子类中,一般会定义与父类相同的属性(数据属性,方法),从而来实现子类特有的行为。也就是说,子类会继承父类的所有的属性和方法,子类也可以覆盖父类同名的属性和方法。
class Parent(object): fooValue = "Hi, Parent foo value" def foo(self): print "This is foo from Parent" class Child(Parent): fooValue = "Hi, Child foo value" def foo(self): print "This is foo from Child" c = Child() c.foo() print Child.fooValue
在这段代码中,子类的属性"fooValue"和"foo"覆盖了父类的属性,所以子类有了自己的行为。
但是,有时候可能需要在子类中访问父类的一些属性:
class Parent(object): fooValue = "Hi, Parent foo value" def foo(self): print "This is foo from Parent" class Child(Parent): fooValue = "Hi, Child foo value" def foo(self): print "This is foo from Child" print Parent.fooValue # use Parent class name and self as an argument Parent.foo(self) c = Child() c.foo()
这时候,可以通过父类名直接访问父类的属性,当调用父类的方法是,需要将"self"显示的传递进去的方式。
这种方式有一个不好的地方就是,需要经父类名硬编码到子类中,为了解决这个问题,可以使用Python中的super关键字:
class Parent(object): fooValue = "Hi, Parent foo value" def foo(self): print "This is foo from Parent" class Child(Parent): fooValue = "Hi, Child foo value" def foo(self): print "This is foo from Child" # use super to access Parent attribute print super(Child, self).fooValue super(Child, self).foo() c = Child() c.foo()
对于"super(Child, self).foo()"可以理解为,首先找到Child的父类Parent,然后调用父类的foo方法,同时将Child的实例self传递给foo方法。
但是,如果当一个子类有多个父类的时候,super会如何工作呢?这是就需要看看MRO的概念了。
假设现在有一个如下的继承结构,首先通过类名显示调用的方式来调用父类的初始化函数:
class A(object): def __init__(self): print " ->Enter A" print " <-Leave A" class B(A): def __init__(self): print " -->Enter B" A.__init__(self) print " <--Leave B" class C(A): def __init__(self): print " --->Enter C" A.__init__(self) print " <---Leave C" class D(B, C): def __init__(self): print "---->Enter D" B.__init__(self) C.__init__(self) print "<----Leave D" d = D()
从输出中可以看到,类A的初始化函数被调用了两次,这不是我们想要的结果:
下面,我们通过super方式来调用父类的初始化函数:
class A(object): def __init__(self): print " ->Enter A" print " <-Leave A" class B(A): def __init__(self): print " -->Enter B" super(B, self).__init__() print " <--Leave B" class C(A): def __init__(self): print " --->Enter C" super(C, self).__init__() print " <---Leave C" class D(B, C): def __init__(self): print "---->Enter D" super(D, self).__init__() print "<----Leave D" d = D()
通过输出可以看到,当使用super后,A的初始化函数只能调用了一次:
为什么super会有这种效果?下面就开始看看Python中的 方法解析顺序 MRO ( Method Resolution Order )。
Python 的类有一个 __mro__ 属性,这个属性中就保存着方法解析顺序。结合上面的例子来看看类 D 的 __mro__:
>>> print "MRO:", [x.__name__ for x in D.__mro__] MRO: ['D', 'B', 'C', 'A', 'object'] >>>
看到这里,对于上面使用 super 例子的输出就应该比较清楚了。
从前面的介绍可以看到,当我们通过一个类创建了实例之后,仍然可以给实例添加属性,但是这些属性只属于这个实例。
有些时候,我们可以需要限制类实例对象的属性,这时就要用到类中的__slots__属性了。__slots__属性对于一个tuple,只有这个tuple中出现的属性可以被类实例使用。
class Student(object): __slots__ = ("name", "age") def __init__(self, name, age): self.name = name self.age = age s = Student("Wilber", 28) print "%s is %d years old" %(s.name, s.age) s.score = 96
在这个例子中,当场是给Student的实例s添加一个score属性的时候,就会遇到下面的异常:
使用__slots__要注意,__slots__定义的属性仅对当前类的实例起作用,对继承的子类实例是不起作用的:
class Person(object): __slots__ = ("name", "age") pass class Student(Person): pass s = Student() s.name, s.age = "Wilber", 28 s.score = 100 print "%s is %d years old, score is %d" %(s.name, s.age, s.score)
从代码的输出可以看到,子类Student的实例并不受父类中__slots__属性的限制:
但是,如果子类本身也有__slots__属性,子类的属性就是自身的__slots__加上父类的__slots__。
class Person(object): __slots__ = ("name", "age") pass class Student(Person): __slots__ = ("score", ) pass s = Student() s.name, s.age = "Wilber", 28 s.score = 100 print "%s is %d years old, score is %d" %(s.name, s.age, s.score) print s.__slots__ s.city = "Shanghai"
代码的输出为:
所以说,对于__slots__属性:
本文介绍了Python中的继承,当使用多继承的时候,可以使用super关键字去访问父类中被子类覆盖的方法;对于方法的调用,需要参照MRO。
另外介绍了Python类的__slots__属性,通过这个属性可以限制类实例的可用属性。