Python self用法詳解

在學習如何定義類的過程中,無論是顯式創建類的構造方法,還是向類中添加實例方法,都要求將 self 參數作爲方法的第一個參數。例如,定義如下 Dog 類:

class Dog:
    def __init__(self):
        print("正在執行構造方法")
    # 定義一個jump()實例方法
    def jump(self):
        print("正在執行jump方法")

本節將對 self 參數做詳細的講解。

Python 要求,類方法(構造方法和實例方法)中至少要包含一個參數,但並沒有規定此參數的名稱(完全可以叫任意參數名),之所以將類方法的第一個參數命名爲 self,只是 Python 程序員約定俗成的一種習慣,這會使程序具有更好的可讀性。

那麼,作爲類方法的第一個參數,self 參數的具體作用是什麼呢?打個比方,如果把類比作造房子的圖紙,那麼對類實例化後的對象纔是真正可以住的房子,根據一張圖紙,我們可以設計出成千上萬的房子,雖然每個房子長相相似,但它們都有各自的主人。而類方法的 self 參數,就相當於每個房子的門鑰匙,它可以保證,每個房子的主人僅能進入自己的房子。

如果你接觸過其他面向對象的編程語言(例如 C++),其實 Python 類方法中的 self 參數就相當於 C++ 中的 this 指針。

也就是說,同一個類可以產生多個對象,當某個對象調用類方法時,該對象會把自身的引用作爲第一個參數自動傳給該方法,換句話說,Python 會自動綁定類方法的第一個參數指向調用該方法的對象。如此,Python解釋器就能知道到底要操作哪個對象的方法了。

對於構造方法來說,self 參數(第一個參數)代表該構造方法正在初始化的對象。

因此,程序在調用實例方法和構造方法時,不需要爲第一個參數傳值。例如,更改前面的 Dog 類,如下所示:

class Dog:
    def __init__(self):
        print(self,"在調用構造方法")
    # 定義一個jump()方法
    def jump(self):
        print(self,"正在執行jump方法")
    # 定義一個run()方法,run()方法需要藉助jump()方法
    def run(self):
        print(self,"正在執行run方法")
        # 使用self參數引用調用run()方法的對象
        self.jump()
dog1 = Dog()
dog1.run()
dog2 = Dog()
dog2.run()

上面代碼中,jump() 和 run() 中的 self 代表該方法的調用者,即誰在調用該方法,那麼 self 就代表誰,因此,該程序的運行結果爲:

<__main__.Dog object at 0x00000276B14B12B0> 在調用構造方法
<__main__.Dog object at 0x00000276B14B12B0> 正在執行run方法
<__main__.Dog object at 0x00000276B14B12B0> 正在執行jump方法
<__main__.Dog object at 0x00000276B14B1F28> 在調用構造方法
<__main__.Dog object at 0x00000276B14B1F28> 正在執行run方法
<__main__.Dog object at 0x00000276B14B1F28> 正在執行jump方法

上面程序中值得一提的是,當一個 Dog 對象調用 run() 方法時,run() 方法需要依賴該對象自己的 jump() 方法。在現實世界裏,對象的一個方法依賴另一個方法的情形很常見,例如,吃飯方法依賴拿筷子方法,寫程序方法依賴敲鍵盤方法,這種依賴都是同一個對象的兩個方法之間的依賴。

注意,當 Python 對象的一個方法調用另一個方法時,不可以省略 self。也就是說,將上面的 run()方法改爲如下形式是不正確的:

# 定義一個run()方法,run()方法需要藉助jump()方法
def run():
    #省略self,代碼會報錯
    self.jump()
    print("正在執行run方法")

再比如,分析如下代碼:

class InConstructor :
    def __init__(self) :
        # 在構造方法裏定義一個foo變量(局部變量)
        foo = 0
        # 使用self代表該構造方法正在初始化的對象
        # 下面的代碼將會把該構造方法正在初始化的對象的foo實例變量設爲6
        self.foo = 6
# 所有使用InConstructor創建的對象的foo實例變量將被設爲6
print(InConstructor().foo) # 輸出6

在 InConstructor 的構造方法中,self 參數總是引用該構造方法正在初始化的對象。程序中將正在執行初始化的 InConstructor 對象的 foo 實例變量設爲 6,這意味着該構造方法返回的所有對象的 foo 實例變量都等於 6。

需要說明的是,自動綁定的 self 參數並不依賴具體的調用方式,不管是以方法調用還是以函數調用的方式執行它,self 參數一樣可以自動綁定。例如如下程序:

class User:
    def test(self):
        print('self參數: ', self) 
u = User()
# 以方法形式調用test()方法
u.test() # <__main__.User object at 0x00000000021F8240>
# 將User對象的test方法賦值給foo變量
foo = u.test
# 通過foo變量(函數形式)調用test()方法。
foo() # <__main__.User object at 0x00000000021F8240>

上面程序中,第 6 行代碼以方法形式調用 User 對象的 test() 方法,此時方法調用者當然會自動綁定到方法的第一個參數(self 參數);程序中第 10 行代碼以函數形式調用 User 對象的 test() 方法,看上去沒有調用者,但程序依然會把實際調用者綁定到方法的第一個參數,因此輸出結果完全相同。

當 self 參數作爲對象的默認引用時,程序可以像訪問普通變量一樣來訪問這個 self 參數,甚至可以把 self 參數當成實例方法的返回值。看下面程序:

class ReturnSelf :
    def grow(self):
        if hasattr(self, 'age'):
            self.age += 1
        else:
            self.age = 1
        # return self返回調用該方法的對象
        return self
rs = ReturnSelf()
# 可以連續調用同一個方法
rs.grow().grow().grow()
print("rs的age屬性值是:", rs.age)

可以看出,如果在某個方法中把 self 參數作爲返回值,則可以多次連續調用同一個方法,從而使得代碼更加簡潔。但是這種把 self 參數作爲返回值的方法可能會造成實際意義的模糊,例如上面的 grow 方法用於表示對象的生長,即 age 屬性的值加 1,實際上不應該有返回值。

發佈了73 篇原創文章 · 獲贊 10 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章