上一篇介紹了Python中類相關的一些基本點,本文看看Python中類的繼承和__slots__屬性。
繼承
在Python中,同時支援單繼承與多繼承,一般語法如下:
1 2 |
class SubClassName(ParentClass1 [, ParentClass2, ...]): class_suite |
實現繼承之後,子類將繼承父類的屬性,也可以使用內建函式insubclass()來判斷一個類是不是另一個類的子孫類:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
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. 如果子類沒有定義自己的初始化函式,父類的初始化函式會被預設呼叫;但是如果要例項化子類的物件,則只能傳入父類的初始化函式對應的引數,否則會出錯。
1 2 3 4 5 6 7 8 9 10 11 12 |
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. 如果子類定義了自己的初始化函式,而沒有顯示呼叫父類的初始化函式,則父類的屬性不會被初始化
1 2 3 4 5 6 7 8 9 10 11 12 |
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. 如果子類定義了自己的初始化函式,顯示呼叫父類,子類和父類的屬性都會被初始化
1 2 3 4 5 6 7 8 9 10 11 12 13 |
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的使用。
在子類中,一般會定義與父類相同的屬性(資料屬性,方法),從而來實現子類特有的行為。也就是說,子類會繼承父類的所有的屬性和方法,子類也可以覆蓋父類同名的屬性和方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
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”覆蓋了父類的屬性,所以子類有了自己的行為。
但是,有時候可能需要在子類中訪問父類的一些屬性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
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關鍵字:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
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
假設現在有一個如下的繼承結構,首先通過類名顯示呼叫的方式來呼叫父類的初始化函式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
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方式來呼叫父類的初始化函式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
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__:
1 2 3 |
>>> 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中出現的屬性可以被類例項使用。
1 2 3 4 5 6 7 8 9 |
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__定義的屬性僅對當前類的例項起作用,對繼承的子類例項是不起作用的:
1 2 3 4 5 6 7 8 9 10 11 12 |
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__。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
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__屬性,通過這個屬性可以限制類例項的可用屬性。