Python_面向對象基礎
由於在學習pytorch的過程中,在定義我們自己的model的時候,用到了很多面向對象的知識,由於先前在學coding的時候,並沒有加重面向對象這方面知識的學習。所以,對於代碼中有些地方的理解還是很生疏,故通過這篇文章,來好好學習下有關python中的面向對象思想。為後面寫出更加完整和具體的pytorch代碼奠定好基礎。
面向對象:
首先從類的定義開始講,我們知道在python中,一切皆對象的這個說法,比如我們在建立一個字元串
a = pythonprint (type(a))
其實a這個字元串是通過對str這個class進行實例化而得到的一個新的對象,它裡面存放的值就是python
那麼,接下來,我們自己寫一個class,來看看到底什麼class。
class Fruits: def __init__(self,name,color): self.name = name self.color = color
通過上面的代碼,我們定義了一個叫做Fruits的類,用這個class,我們就可以生成一系列的水果來,其中的self需要好好的理解下,並且__init__不能寫錯,必須是這樣的格式。
apple = Fruits(apple,red)banana = Fruits(banana,yellow)
很多讀者可能會問,那麼上述代碼片段中的self表示什麼含義呢?
其實self我們可以先這樣理解,把它看做是一個在我們定義好class後,通過實例化後的到的那個個體,就用self來代替。是不是感覺很繞?來用例子告訴大家。
我們在對Fruits這個類進行實例化後,得到了一個叫做apple的對象,那麼此時,跳進class的內部結構,一開始執行的就是__init__()這個方法,那麼,此時接受的必備參數中的self,就是我們的apple。下面的self.name,實際上就是apple.name,self.color實際就是apple.color。
那麼,講到這裡,大家應該明白了__init__()方法的作用了吧。在我們進行實例化的時候,必須要藉助這個初始化函數__init__()來實現。如果沒有這個函數,就算我們定義了多麼perfect的class,也是沒辦法進行實例化的。
再來看進一步的理解。
class Fruits: def __init__(self,name,color=black): self.name = name self.color = color
當我們給這個參數賦值了默認值後,
- 如果我們在實例化的時候,沒有傳入任何有關color的信息,那麼self.color的值就是這個默認值。
- 如果在實例化的時候,傳入了有關color的信息,那麼self.color的值就會被我們傳入的值所代替
總結:
- class是定義一個類的關鍵字
- 每個類都必須有個類名,類名一般首字母大寫
- object是父類名,object是一切類的基類,在python3中,如果你要繼承的那個類是基類,可以不用寫object,也就是說,class Fruits(): class Fruits: class Fruits(object)在python3中都是正確的。
類的初始化:
類的初始化主要講解的就是__init__()
定義類時,這種方法可以使類對象實例按照某種特定的模式生產出來。
- 上面這句話的解釋可以用生活中做月餅的例子來講。假如我現在有一個做月餅的模板,只要把一定量的麵粉和一定量的果餡放進這個模板里,那麼,他就會產生我們需要口味的月餅。那麼__init__其實也是起的這個作用,比如我們要實例化一個類,得到相應的對象時,就是通過這樣的方式得到的。
- 一般__init__()後面的第一個參數還是默認為self,這是約定俗成的
- 除了self這個參數外,其他的參數定義也必須遵循函數的必備參數和默認參數的原則
- 必備參數:在實例化的時候必須要傳入的參數
- 默認參數:在實例化的時候可以傳也可以不傳的參數
類的實例化:
- 基本形式:實例對象名 = 類名 (參數)
- 在實例化的過程中,self表示的就是這個實例對象本身的名字
- 實例化的時候,會把類名後面的接的參數傳進去賦值給實例
- 這些傳進去的參數就成為了這個實例對象的屬性
上面這幾條規則是不是對新手來說有些不友好?別怕,一句一句解釋下。
self的意義,在前面已經講過了,這裡要說的是同前面一個意思。
比如說:
apple2 = Fruits(apple,red)
那麼,apple,red這兩個參數會被傳進class Fruits:這個類中,作為__init__的兩個參數,那麼這兩個參數一旦傳入成功,這個實例對象apple2就多了兩個屬性,一個屬性是:self.name
一個屬性是:self.color
- 實例化的過程遵循函數調用的原則
- 在實例化時,參數的個數和順序的定義必須相同(使用關鍵詞參數可以改變傳入參數的順序)
- 當__init__定義的時候,如果採用了默認參數,那麼在實例化的時候,默認參數可以不傳遞,這個實例化對象就會使用默認的參數,將這個默認的參數變成他的屬性。如果傳了參數進去,就會改變這個值,那麼所對應的實例化對象的屬性也會被改變。
- 我們可以通過isinstance( object 1 , class1) 來判斷object 1是不是class1的子類,也就說,object1是不是通過class1進行實例化得到的對象
類和實例屬性:
- 類屬性,是可以直接通過 類名.屬性名 來訪問和修改的。比如
class Fruits: id = xxxxx def __init__(self,name,color=yellow): self.name = name self.color = color def eat(self): print (Oh, it is eaten!)print (Fruits.id)
我們就能夠得到 xxxxx 的顯示。此時的這個id就是一個類的屬性
class Fruits: id = xxxxx def __init__(self,name,color=yellow): self.name = name self.color = color def eat(self): print (Oh, it is eaten!)print (Fruits.id)Fruits.id = asdasdasdprint (Fruits.id)
接下來,我們直接對這個類的屬性進行修改,可以發現,修改成功了。
2. 類屬性是這個類的所有實例對象所共有的屬性
class Fruits: id = xxxxx def __init__(self,name,color=yellow): self.name = name self.color = color def eat(self): print (Oh, it is eaten!)apple = Fruits(apple,red)banana = Fruits(banana)print (apple.id,banana.id)
通過上面的代碼,我們就可以參數,apple,banana都是通過Fruits這個類得到的實例化對象,那麼這兩個對象都可以調用Fruits類的id這個屬性
3. 任意一個實例對象都可以訪問並且修改這個屬性(私有隱私除外)
class Fruits: id = xxxxx def __init__(self,name,color=yellow): self.name = name self.color = color def eat(self): print (Oh, it is eaten!)apple = Fruits(apple,red)banana = Fruits(banana)print (apple.id,banana.id)banana.id = yyyyyprint (apple.id,banana.id,Fruits.id)
通過上面的代碼,我們可以發現。分別實例化後的兩個對象apple,banana都具有id這個屬性,此時的id這個屬性就是類的共有屬性。但是在實例化後,只通過實例banana.id來進行修改id的值,那麼apple.id的值是並不會發生改變的,Fruits.id的值也不會發生改變的。說明了任意一個實例對象都可以訪問並且修改這個屬性,但是不同類之間以及這個類本身的該屬性並不會受到影響。
4. 對類屬性的修改,遵循基本數據類型的特性,列表可以直接進行修改,字元串不可以。
class Fruits: id = xxxxx list1 = [] def __init__(self,name,color=yellow): self.name = name self.color = color self.list2 = [xxxx] def eat(self): print (Oh, it is eaten!)apple = Fruits(apple,red)banana = Fruits(banana)print (apple.id,Fruits.id)print(apple.list1,apple.list2,banana.list2,Fruits.list1)apple.list1.append(bai)apple.id = yyyybanana.list2.append(zzzz)print (apple.id,Fruits.id)print (apple.list1,apple.list2,banana.list2,Fruits.list1)
這部分的內容就需要好好理解一下了。
首先從這個class本身的定義開始挖掘吧。
類內有一個屬性叫做id,是字元串類型。一個屬性叫做list1,是列表類型。
並且,在__init__裡面,也還有一個屬性,是list2,也是列表類型。我們重點來觀察這幾個值的改變情況。
上述文字中,列表可以直接修改,而字元串不能直接修改。說的就是
我們在實例化後,通過apple.list1.append(bai)就可以給list1賦一個新的值,那麼不管是我們列印apple.list1 還是列印 banana.list1 還是列印Fruits.list1,其中list1的值都被賦了這個新的數值。
而當我們對apple.id的值進行改變的時候,我們會發現,只有apple.id的值發生了改變,其他banana.id和Fruits.id的值都沒有發生改變。
而在__init__中的list2的值,也必須通過對其實例化後的對象進行修改的方法才能得到改變。
5. 實例屬性
在屬性前面加了self標識符的屬性就是實例屬性
6. 方法屬性
定義屬性方法的內容是函數,函數的第一個參數是self,代表實例本身
7.一些小的tricks
數據的屬性會覆蓋同名的方法屬性,為了減少衝突,可以方法使用動詞,數據屬性使用名詞。
數據屬性可以被方法所引用
class Fruits: id = xxxxx list1 = [] def __init__(self,name,color=yellow): self.name = name self.color = color self.list2 = [xxxx] def eat(self): print (Oh, it is eaten! %s%self.name)apple = Fruits(apple,red)banana = Fruits(banana)apple.eat()
這個例子就表明了,數據的屬性是可以被方法所引用的。在eat方法中,我們調用了self.name來獲得了數據的屬性
使用.__dict__可以看到所有的類中的屬性和實例屬性
class Fruits: id = xxxxx list1 = [] def __init__(self,name,color=yellow): self.name = name self.color = color self.list2 = [xxxx] def eat(self): print (Oh, it is eaten! %s%self.name)apple = Fruits(apple,red)banana = Fruits(banana)apple.eat()print (apple.__dict__)print (Fruits.__dict__)
如果在一個類中,使用__在某個屬性名的前面,可以將這個屬性隱藏起來。
class Fruits: id = xxxxx list1 = [] def __init__(self,name,color=yellow,weight = 90): self.name = name self.color = color self.__weight = weight self.list2 = [xxxx] def eat(self): print (Oh, it is eaten! %s%self.name) def shiyong(self): print (%s%self.__weight)apple = Fruits(apple,red)banana = Fruits(banana)print (apple._Fruits__weight)apple.shiyong()
在類的外部必須通過_Fruits__weight才能找到先前的weight,而不是通過apple.weight找到。
但是在類的內部,是可以通過self.__weight找到的
數據封裝:
數據封裝其實非常簡單,就是在類裡面通過數據屬性和行為用函數的形式封裝起來
訪問的時候直接調用,不需要知道類裡面具體的實現方法
繼承:
class Fruits: id = xxxxx list1 = [] def __init__(self,name,color=yellow,weight = 90): self.name = name self.color = color self.__weight = weight self.list2 = [xxxx] def eat(self): print (Oh, it is eaten! %s%self.name) def shiyong(self): print (%s%self.__weight)class Apple(Fruits): passapple = Apple(apple)apple2 = Fruits(apple)print (apple.id == apple2.id)
繼承其實也不難,在上面的例子中,我們定義了一個叫做Fruits的類,這個時候,我們在定義一個叫做Apple的類,但是這個類必須要繼承自Fruits。當我們不進行任何操作的時候,我們發現,通過父類還是直接通過此類本身實例化後的對象具有相同的屬性值和方法
可以通過子類的方法來重寫父類方法
class Fruits: id = xxxxx list1 = [] def __init__(self,name,color=yellow,weight = 90): self.name = name self.color = color self.__weight = weight self.list2 = [xxxx] def eat(self): print (Oh, it is eaten! %s%self.name) def shiyong(self): print (%s%self.__weight)class Apple(Fruits): def eat(self): print(delicious)apple = Apple(apple)apple.eat()
上面這段代碼就說明了,我們可以通過子類來重寫父類的方法。
在定義類的時候,可以從已有的類繼承,
- 被繼承的類稱為基類,新定義的類稱為派生類,object是一切類的基類
- 在類中找不到調用的屬性時,就去向上搜索基類,如果基類是從別的類中派生而來的,那麼會遞歸的應用上去
- 如果派生類的屬性和基類重名,那麼派生類的屬性會覆蓋掉基類的屬性,包括初始化函數
- 可以同時使用『類名+__init__(arg)』或者super()函數來幫助派生類在初始化函數中完成繼承和修改的過程。
emmm. 其實關於繼承的知識,我們只要想清楚父類和子類的關係就能夠很好地把握。比如說我現在有一個父類class A, 有一個子類class B.也就是說,子類的生成是通過父類的繼承。
那麼在繼承的過程中,其實有三種情況。
- 子類全部繼承父類的所有屬性和方法
- 子類不繼承父類的屬性和方法,而是對其進行重寫
- 子類既要繼承父類的屬性和方法,並且也要擁有自己獨特的屬性和方法
下面會將這三種情況進行詳細論述
第一種情況:
class Fruits: id = xxxxx list1 = [] def __init__(self,name,color=yellow,weight = 90): self.name = name self.color = color self.__weight = weight self.list2 = [xxxx] def eat(self): print (Oh, it is eaten! %s%self.name) def shiyong(self): print (%s%self.__weight)class Apple(Fruits): passapple = Apple(apple)apple2 = Fruits(apple)print (apple.id == apple2.id)
通過一個pass,因為派生類內部是一個pass,裡面什麼都沒有,它將會把基類的所有屬性和方法拿來用。
第二種情況:
class Fruits: id = xxxxx list1 = [] def __init__(self,name,color=yellow,weight = 90): self.name = name self.color = color self.__weight = weight self.list2 = [xxxx] def eat(self): print (Oh, it is eaten! %s%self.name) def shiyong(self): print (%s%self.__weight)class Apple(Fruits): def eat(self): print(delicious)apple = Apple(apple)apple.eat()
這時候派生類裡面有一個eat()的方法,該方法和基類中eat()的方法重名了。那麼此時,我們就必須滿足,在派生類中的方法覆蓋掉基類中的同名方法。調用apple.eat()的時候,列印出來的就是delicios.
第三種情況比較特殊:
class Fruits: id = xxxxx list1 = [] def __init__(self,name,color=yellow,weight = 90): self.name = name self.color = color self.__weight = weight self.list2 = [xxxx] def eat(self): print (Oh, it is eaten! %s%self.name) def show(self): print (%s%self.__weight)class Apple(Fruits): def __init__(self,shape): #Fruits.__init__(self,apple) super().__init__(apple) self.shape = shape def eat(self): print(delicious) def show(self): #super().show() Fruits.show(self) print(aaaaaaa)apple = Apple(round)print (apple.shape,apple.name,apple.color,apple._Fruits__weight)apple.eat()apple.show()
這種情況是指,我派生類既要擁有一套自己的屬性和方法,同時我還要把基類的屬性和方法拿過來用一用。這個過程中,就涉及到了兩種不同的方式來達到相同的結果。
- 第一種方式是: 基類名.__init__(self,args)來實現
- 第二種方式是:super().__init__(args)來實現
這兩種方式有什麼區別呢?目前記住可以互相調換使用就行了,但是在日常寫代碼的過程中,我發現super的方法還是使用的更多一些。因為基類名.__init__(self,args)這種方法就類似於在一個派生類中去調用基類的__init__屬性。
這裡可以通過issubclass(類名1,類名2)來判斷類1是否繼承了類2
多態:
多態是基於繼承的一個好處。
當派生類重寫了基類的時候,就說實現了多態的特性。
關於python面向對象的基礎,就先寫這麼多。關於更深一步的內容,我們後面再慢慢學習。
推薦閱讀:
※關於PyTorch在Windows下的CI和whl包
※PyTorch初探遷移學習
※pytorch例子-實現卷積網路進行圖像分類
※PyTorch源碼淺析(四)
※用PyTorch構建卷積神經網路