解開Python中self的四個秘密
self的謎團
毫無疑問,幾乎每一門Python課程都有一個關于(class)類的講座——面向對象編程語言的基本構建模塊之一。
當您通過一些示例學習它時,您將注意到在Python類中定義的許多函數都將self作為它們的第一個參數。例如,在下面的代碼片段中,我們聲明了一個名為Student的類,它的greet()方法的第一個參數是self。但是,函數根本沒有使用self,所以這里的self到底是從哪里來的呢?這對許多初學者來說是第一個謎。
- >>> class Student:
- ... def greet(self, name):
- ... print('Good Morning, ' + name)
- ...
- >>> student = Student()
- >>> student.greet('John')
- Good Morning, John
同樣奇怪的是,當我們使用這個函數時,我們并沒有給self參數設置任何東西,這是困擾我們的另一個謎題。在這篇文章中,我們將與學習者分享一些Python中self的奧秘。
1. 它代表什么?
在開始處理這個謎題之前,我們需要理解兩個基本的相關概念:類和實例。當然,解決所有這些謎題需要額外的知識,而不僅僅是類和實例,我將在接下來的討論中澄清這一點。如果你對這兩個概念都很了解,你可以跳過下一段,這段只是對這兩個概念的簡要概述。
創建Python類就是聲明一種新的對象類型,它提供了一種將數據和功能捆綁在一起的機制。在上面的示例中,我們創建了一個名為Student的類,并使用它創建了一個名為Student的學生類型的對象。這個對象被稱為student類的實例。此外,類還可以提供通常稱為屬性的特定功能,例如示例中的greet()函數。我們使用三個內省(introspection)函數(type()、isinstance()和hasattr())來檢查相關信息。
- >>> type(Student)
- <class 'type'>
- >>> type(student)
- <class '__main__.Student'>
- >>> isinstance(student, Student)
- True
- >>> hasattr(Student, 'greet')
- True
我可以簡單地告訴您,greet()函數中的self參數是上面示例中的student實例。更一般地說,是實例調用這個函數。以下是支持證據:
- >>> class Student:
- ... def greet(self, name):
- ... print(id(self))
- ... print('Good Morning, ' + name)
- ...
- >>> student = Student()
- >>> student.greet('John')
- 4546580944
- Good Morning, John
- >>> id(student)
- 4546580944
在上面的代碼中,我們修改了greet()函數,要求它使用內省id()函數向我們顯示self參數的內存地址。如您所見,self參數和實例student是同一個對象,因為它們具有相同的內存地址。

2. 為什么不需要在函數調用中設置它呢?
繼續上一節中展示的示例,當我們使用實例student調用greet()函數時,這個函數通常被稱為實例方法——一個對某個類的實例可用的函數。但是,如果我們檢查這個屬性的類型,就會顯示一些不同的東西。
- >>> student = Student()
- >>> student.greet
- <bound method Student.greet of <__main__.Student object at 0x10eff5750>>
如上所述,實例student的greet屬性稱為綁定方法。具體來說,它被綁定到Student類的greet屬性。
為了準確理解這意味著什么,讓我們看看下面的代碼:
- >>> Student.greet(student, 'John')
- Good Morning, John
結合開頭的示例,您可能會注意到這段代碼中的三件事:
- 這個函數的調用者是類Student,而不是實例student。
- 在這個調用中設置了self和name參數,這與student調用初始函數時忽略self參數不同。
- 兩個函數調用都產生了相同的輸出。它們本質上用的是同一個函數。
通過實現這些信息,您可能已經猜到在使用實例student調用greet()函數時,幕后發生了什么。

如上圖所示,當實例student調用greet(' John ')方法時,解釋器將處理此函數調用,作為類Student將調用者(即實例student)和name參數(即' John ')發送給greet(self, name)函數,該函數打印“Good Morning, John”。
對于感興趣的讀者,這里有幾件事要知道,可以幫助你更深入地了解這個謎。當創建一個Python類時,它聲明的函數就是這個類的屬性(稱為函數對象)。換句話說,類“擁有”這些函數。類的實例不會直接實現這些函數。相反,它們將具有與類中實現的相應函數綁定的相同屬性(即實例方法)。
3.self是一個關鍵詞嗎?
似乎在所有這些已定義的函數中,我們都使用self作為它們的第一個參數。有些人可能錯誤地認為self是Python為這些用例保留的關鍵字。然而,事實并非如此。請看下面一個簡單的例子:
- >>> def=5
- File "<stdin>", line 1
- def=5
- ^
- SyntaxError: invalid syntax
- >>> class=4
- File "<stdin>", line 1
- class=4
- ^
- SyntaxError: invalid syntax
- >>> self=3
你可能知道,def和class是Python中的關鍵字,我們不能用它們作為變量名。然而,我們可以在定義函數的上下文之外使用self作為變量名,這表明它在Python中不是保留關鍵字。

4. 我們必須在這些函數聲明中使用self嗎?
在上面的例子中,我們重復引用了greet()函數。正如我們已經討論過的,我們將這個函數實現為一個實例方法,這樣它就可以被這個Student類的所有實例使用。在這種情況下,self是必需的。下面是一些證據:
- >>> class Teacher:
- ... def say_hello(name):
- ... print('Hello, ' + name)
- ...
- >>> teacher = Teacher()
- >>> teacher.say_hello('John')
- Traceback (most recent call last):
- File "<stdin>", line 1, in <module>
- TypeError: say_hello() takes 1 positional argument but 2 were given
這里有一些分析。如前所述,當實例teacher調用say_hello()方法時,發生的事情是執行teacher .say_hello()函數,并將實例對象teacher和' John '設置為函數調用。這就是為什么錯誤說“2是給定的。這與函數的定義相反,函數的定義只有一個參數(name)。
然而,還有兩件事與這個謎有關,你可能想知道:
- 雖然聲明實例方法需要包含self參數,但它不必命名為self。在這個場景中使用這個名稱只是每個Python程序員都能欣賞的一種約定。下面是一個例子,它可以被命名為其他東西而不會引起任何問題。盡管它在語法上是正確的,但不推薦使用,因為它只會讓其他Python程序員感到困惑:
- >>> class Teacher:
- ... def say_hello(professor, name):
- ... print('Hello, ' + name)
- ...
- >>> teacher = Teacher()
- >>> teacher.say_hello('John')
- Hello, John
- 在聲明其他函數(如類和靜態方法)時,不需要使用self參數。對類和靜態方法的清晰解釋將是以后文章的主題。但我在這里可以展示的是,當我們聲明一個類方法時,函數確實有一些類似于在實例方法中使用self的東西,它通常被稱為cls,引用類對象本身。它與具體實例無關。下面是一個例子:
- >>> class Student:
- ... def __init__(self, name):
- ... self.name = name
- ... @classmethod
- ... def with_names(cls, first_name, last_name):
- ... return cls(first_name + ' ' + last_name)
- ...
- >>> student = Student.with_names('John', 'Smith')
- >>> student.name
- 'John Smith'
英文原文:
Unlock the 4 Mysteries of self in Python | by Yong Cui | Better Programming | Medium