self參數
self指的是實例Instance本身,在Python類中規定,函數的第一個參數是實例對象本身,並且約定俗成,把其名字寫為self,
也就是說,類中的方法的第一個參數一定要是self,而且不能省略。關於self有三點是很重要的:
self指的是實例本身,而不是類
self可以用this替代,但是不要這么去寫
類的方法中的self不可以省略
class Person():
def eat(self):
print(self)
Alex = Person()
Alex.eat()
print(Person)
__ init__ ()方法
在python中創建類后,通常會創建一個 __ init__ ()方法,這個方法會在創建類的實例的時候自動執行。
__ init__ ()方法必須包含一個self參數,而且要是第一個參數。
class Person():
def __init__(self):
print("是一個人")
#self.name = name
def eat(self):
print("吃飯")
Alex = Person()
是一個人
__ init__ ()方法就已經自動執行了,但是如果不是 __ init__ ()方法,比如說eat()方法,那肯定就只有調用才執行
再比如說下面的代碼,如果 __ init__ ()方法中還需要傳入另一個參數name,但是
我們在創建Alex的實例的時候沒有傳入name,那么程序就會報錯,
class Person():
def __init__(self,name):
print("是一個人")
self.name = name
def eat(self):
print("吃飯")
Alex = Person()
我們其實就比較清晰的知道什么東西需要在__ init__ ()方法中定義了,就是希望有一些操作是在創建實例的時候就有,
我們寫神經網絡的代碼的時候,一些網絡結構的設置,也最好放在__ init__ ()方法中。
super(Net, self).init()
python中的super(Net, self).init()是指首先找到Net的父類(比如是類NNet),然后把類Net的對象self轉換為類NNet的對象,然后“被轉換”的類NNet對象調用自己的init函數,
其實簡單理解就是子類把父類的__init__()放到自己的__init__()當中,這樣子類就有了父類的__init__()的那些東西。
Net類繼承nn.Module,super(Net, self).init()就是對繼承自父類nn.Module的屬性進行初始化。
而且是用nn.Module的初始化方法來初始化繼承的屬性。
class Net(nn.Module): def __init__(self): super(Net, self).__init__() # 輸入圖像channel:1;輸出channel:6;5x5卷積核 self.conv1 = nn.Conv2d(1, 6, 5)
也就是說,子類繼承了父類的所有屬性和方法,父類屬性自然會用父類方法來進行初始化。
當然,如果初始化的邏輯與父類的不同,不使用父類的方法,自己重新初始化也是可以的。比如:
#!/usr/bin/env python # -*- coding:utf-8 -*- class Person(object): def __init__(self,name,gender,age): self.name = name self.gender = gender self.age = age class Student(Person): def __init__(self,name,gender,age,school,score): #super(Student,self).__init__(name,gender,age) self.name = name.upper() self.gender = gender.upper() self.school = school self.score = score s = Student('Alice','female',18,'Middle school',87) print (s.school) print (s.name)
print (s.age) #報錯,因為沒有初始化
Middle school
ALICE
class Person(object):#父類 def __init__(self, name, gender, age): self.name = name self.gender = gender self.age = age class Student(Person): def __init__(self, name, gender, age, school, score): super(Student,self).__init__(name,gender,age) # self.name = name.upper() # self.gender = gender.upper() self.school = school self.score = score s = Student('Alice', 'female', 18, 'Middle school', 87) print(s.school) print(s.name)
Middle school
Alice
class Person(object): def __init__(self, name, gender, age): self.name = name self.gender = gender self.age = age +2 class Student(Person): def __init__(self, name, gender, age, school, score): super(Student,self).__init__(name,gender,age) # self.name = name.upper() # self.gender = gender.upper() self.school = school self.score = score s = Student('Alice', 'female', 18, 'Middle school', 87) print (s.school) print (s.age)
Middle school
20 #(18+2)
class Person(object): def __init__(self, name, gender, age): self.name = name self.gender = gender self.age = age +2 class Student(Person): def __init__(self, name, gender, age, school, score): super(Student,self).__init__(name,gender,age) self.name = name.upper()#繼承后仍可以修改 # self.gender = gender.upper() self.school = school self.score = score s = Student('Alice', 'female', 18, 'Middle school', 87) print (s.name) print (s.age)
ALICE
20
def __init__(self, name, gender, age, school, score):
super(Student,self).__init__(name,age,gender)#報錯,順序要匹配
def __init__(self, name, gender, age, school, score):
super(Student,self).__init__(name,gender)#報錯,不可缺少父類屬性
下面解釋Python的super()函數
1.python子類調用父類成員有2種方法,分別是普通方法和super方法
假設Base是基類
class Base(object):
def __init__(self):
print “Base init”
則普通方法如下
class Leaf(Base):
def __init__(self):
Base.__init__(self)
print “Leaf init”
super方法如下
class Leaf(Base):
def __init__(self):
super(Leaf, self).__init__()
print “Leaf init”
兩種方法的效果一樣
我們就遇到了一個難題:
class Base(object): def __init__(self): print “Base init” class Medium1(Base): def __init__(self): Base.__init__(self) print “Medium1 init” class Medium2(Base): def __init__(self): Base.__init__(self) print “Medium2 init” class Leaf(Medium1, Medium2): def __init__(self): Medium1.__init__(self) Medium2.__init__(self) print “Leaf init” leaf =Leaf()
Base被初始化了兩次!這是由於Medium1和Medium2各自調用了Base的初始化函數導致的。
解決方法:
Python使用的是super重寫一下,看一下輸出結果
class Base(object): def __init__(self): print “Base init” class Medium1(Base): def __init__(self): super(Medium1, self).__init__() print “Medium1 init” class Medium2(Base): def __init__(self): super(Medium2, self).__init__() print “Medium2 init” class Leaf(Medium1, Medium2): def __init__(self): super(Leaf, self).__init__() print “Leaf init”
leaf =Leaf()
Base init
Medium2 init
Medium1 init
Leaf init
super的內核:mro
要理解super的原理,就要先了解mro。mro是method resolution order的縮寫,表示了類繼承體系中的成員解析順序。
在python中,每個類都有一個mro的類方法。我們來看一下鑽石繼承中,Leaf類的mro是什么樣子的:
>>> Leaf.mro()
[<class '__main__.Leaf'>, <class '__main__.Medium1'>, <class '__main__.Medium2'>, <class '__main__.Base'>, <type 'object'>]
可以看到mro方法返回的是一個祖先類的列表。Leaf的每個祖先都在其中出現一次,這也是super在父類中查找成員的順序。
通過mro,python巧妙地將多繼承的圖結構,轉變為list的順序結構。super在繼承體系中向上的查找過程,變成了在mro中向右的線性查找過程,任何類都只會被處理一次。
通過這個方法,python解決了多繼承中的2大難題:
1. 查找順序問題。從Leaf的mro順序可以看出,如果Leaf類通過super來訪問父類成員,那么Medium1的成員會在Medium2之前被首先訪問到。
如果Medium1和Medium2都沒有找到,最后再到Base中查找。
2. 鑽石繼承的多次初始化問題。在mro的list中,Base類只出現了一次。事實上任何類都只會在mro list中出現一次。
這就確保了super向上調用的過程中,任何祖先類的方法都只會被執行一次。
super的具體用法
(1) super(type, obj)
class Leaf(Medium1, Medium2): def __init__(self): super(Leaf, self).__init__() print “Leaf init”
super(Leaf, self).__init__()的意思是說:
- 獲取self所屬類的mro, 也就是[Leaf, Medium1, Medium2, Base]
- 從mro中Leaf右邊的一個類開始,依次尋找__init__函數。這里是從Medium1開始尋找
- 一旦找到,就把找到的__init__函數綁定到self對象,並返回
從這個執行流程可以看到,如果我們不想調用Medium1的__init__,而想要調用Medium2的__init__,那么super應該寫成:super(Medium1, self)__init__()
(2) super(type, type2)
class Leaf(Medium1, Medium2): def __new__(cls): obj = super(Leaf, cls).__new__(cls) print “Leaf new” return obj
super(Leaf, cls).__new__(cls)的意思是說:
- 獲取cls這個類的mro,這里也是[Leaf, Medium1, Medium2, Base]
- 從mro中Leaf右邊的一個類開始,依次尋找__new__函數
- 一旦找到,就返回“非綁定”的__new__函數
由於返回的是非綁定的函數對象,因此調用時不能省略函數的第一個參數。這也是這里調用__new__時,需要傳入參數cls的原因
同樣的,如果我們想從某個mro的某個位置開始查找,只需要修改super的第一個參數就行