上一篇介紹了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__屬性是不能從父類中繼承來的。
繼承中的__init__
當在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
前面一個例子中,已經看到了通過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的概念了。
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例子的輸出就應該比較清楚了。
- Python的多繼承類是通過MRO的方式來保證各個父類的函數被逐一調用,而且保證每個父類函數只調用一次(如果每個類都使用super)
- 混用super類和非綁定的函數是一個危險行為,這可能導致應該調用的父類函數沒有調用或者一個父類函數被調用多次
__slots__
從前面的介紹可以看到,當我們通過一個類創建了實例之后,仍然可以給實例添加屬性,但是這些屬性只屬於這個實例。
有些時候,我們可以需要限制類實例對象的屬性,這時就要用到類中的__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__要注意,__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__加上父類的__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__屬性:
- 如果父類包含對__slots__的定義,子類不包含對__slots__的定義,解釋器忽略__slots__的作用
- 如果父類包含對__slots__的定義,子類包含對__slots__的定義,並且無論元組的的元素個數,解釋器都會按照父類的__slots__和子類的__slots__的並集來檢查
總結
本文介紹了Python中的繼承,當使用多繼承的時候,可以使用super關鍵字去訪問父類中被子類覆蓋的方法;對於方法的調用,需要參照MRO。
另外介紹了Python類的__slots__屬性,通過這個屬性可以限制類實例的可用屬性。