Day125:Python中self用法詳解

我們知道,面向物件最重要的概念就是類(class)和例項(instance),類是抽象的模板,比如學生這個抽象的事物,可以用一個Student類來表示。而例項是根據類創建出來的一個個具體的“物件”,每一個物件都從類中繼承有相同的方法,但各自的資料可能不同。

1、定義類

以Student類為例,在Python中,定義類如下:

class Student(object): pass

(Object)表示該類從哪個類繼承下來的,Object類是所有類都會繼承的類。

即class Name(*)中*是表示從哪個類繼承下來的

2、由類建立例項

定義好了類,就可以透過Student類創建出Student的例項,建立例項是透過類名+()實現:

student = Student()

3、新增屬性

由於類起到模板的作用,因此,可以在建立例項的時候,把我們認為必須繫結的屬性強制填寫進去。這裡就用到Python當中的一個內建方法__init__方法,例如在Student類時,把name、score等屬性綁上去:

class Student(object): slogan = ‘I ame a student’ # 類屬性:類名訪問 def __init__(self, name, score): self。name =i name # 例項屬性 :例項與類名均可訪問 self。score = score # 例項屬性

__init__方法的第一引數永遠是self,表示類的例項本身

,因此,在__init__方法內部,就可以把各種屬性繫結到self,因為self就指向建立的例項本身

(透過self繫結的屬性是例項屬性)

另外,這裡self就是指由類建立的例項本身,self。name是Student類例項本身的屬性變數,而name是外部傳來的引數,所以,self。name = name的意思就是把外部傳來的引數name的值賦值給Student類例項本身自己的屬性變數self。name

有了__init__方法,在建立例項的時候,就不能傳入空的引數了,必須傳入與__init__方法匹配的引數,

但self不需要傳,Python直譯器會自己把例項變數傳進去

>>>student = Student(“Hugh”, 99)>>>student。name“Hugh”>>>student。score99

4、類中的函式與普通函式作比較

和普通函式相比,在類中定義函式只有一點不同,就是第一引數永遠是類的本身例項變數self,並且呼叫時,不用傳遞該引數

。除此之外,類的方法(函式)和普通函式沒啥區別,你既可以用預設引數、可變引數或者關鍵字引數(*args是可變引數,args接收的是一個tuple,**kw是關鍵字引數,kw接收的是一個dict)。

5、類的方法

既然Student類例項本身就擁有這些資料,那麼要訪問這些資料,就沒必要從外面的函式去訪問,而可以直接在Student類的內部定義訪問資料的函式(方法),這樣,就可以把”資料”封裝起來。這些封裝資料的函式是和Student類本身是關聯起來的,稱之為

類的方法(類例項與類均可訪問)

class Student(obiect): def __init__(self, name, score): self。name = name self。score = score def print_score(self): print “%s: %s” % (self。name, self。score)

>>>student = Student(“Hugh”, 99)>>>student。print_scoreHugh: 99

這樣一來,我們從外部看Student類,建立例項需要給出name和score。而如何列印,都是在Student類的內部定義的,這些資料和邏輯被封裝起來了,呼叫很容易,但卻不知道內部實現的細節。

如果要讓內部屬性不被外部訪問,可以把屬性的名稱前加上兩個下劃線‘__’,

在Python中,例項的變數名如果以__開頭,就變成了一個私有變數(private),只有內部可以訪問,外部不能訪問

,所以,我們把Student類改一改:

class Student(object): def __init__(self, name, score): self。__name = name self。__score = score def print_score(self): print “%s: %s” %(self。__name,self。__score)

改完後,對於外部程式碼來說,沒什麼變動,但是已經無法從外部訪問例項變數。__name和例項變數。__score了:

>>> student = Student(‘Hugh’, 99)>>> student。__nameTraceback (most recent call last): File “”, line 1, in AttributeError: ‘Student’ object has no attribute ‘__name’

這樣就確保了外部程式碼不能隨意修改物件內部的狀態,這樣透過訪問限制的保護,程式碼更加健壯。

但是如果外部程式碼要獲取name和score怎麼辦?可以給Student類增加get_name和get_score這樣的方法:

class Student(object): 。。。 def get_name(self): return self。__name def get_score(self): return self。__score

如果又要允許外部程式碼修改score怎麼辦?可以給Student類增加set_score方法:

class Student(object): 。。。 def set_score(self, score): self。__score = score

需要注意的是,

在Python中,變數名類似__xxx__的,也就是以雙下劃線開頭,並且以雙下劃線結尾的,是特殊變數,特殊變數是可以直接訪問的,不是private變數,所以,不能用__name__、__score__這樣的變數名

有些時候,你會看到以一個下劃線‘_’開頭的例項變數名,比如_name,這樣的例項變數外部是可以訪問的,但是,按照約定俗成的規定,當你看到這樣的變數時,意思就是,“雖然我可以被訪問,但是,請把我視為私有變數,不要隨意訪問”。

封裝的另一個好處是可以隨時給Student類增加新的方法

,比如:get_grade:

class Student(object): 。。。 def get_grade(self): if self。score >= 90: return ‘A’ elif self。score >= 60: return ‘B’ else: return ‘C’

同樣的,get_grade方法可以直接在例項變數上呼叫,不需要知道內部實現細節:

>>> student。get_grade()‘A’

6、self的仔細用法

(1)、self代表類的例項,而非類

class Test: def ppr(self): print(self) print(self。__class__)t = Test()t。ppr()執行結果:<__main__。Test object at 0x000000000284E080>

從上面的例子中可以很明顯的看出,self代表的是類的例項。而self.__class__則指向類。

注意:把self換成this,結果也一樣,但Python中最好用約定俗成的self。

(2)self可以不寫嗎?

在Python直譯器的內部,當我們呼叫t.ppr()時,實際上Python解釋成Test.ppr(t),也就是把self替換成了類的例項

class Test: def ppr(): print(self)t = Test()t。ppr()執行結果如下:Traceback (most recent call last): File “cl。py”, line 6, in t。ppr()TypeError: ppr() takes 0 positional arguments but 1 was given

執行時提醒錯誤如下:

ppr在定義時沒有引數,但是我們執行時強行傳了一個引數。由於上面解釋過了t.ppr()等同於Test.ppr(t),所以程式提醒我們多傳了一個引數t。這裡實際上已經部分說明了self在定義時不可以省略

當然,如果我們的定義和呼叫時均不傳類例項是可以的,這就是類方法

class Test: def ppr(): print(__class__)Test。ppr()執行結果:

(3)在繼承時,傳入的是哪個例項,就是那個傳入的例項,而不是指定義了self的類的例項。

class Parent: def pprt(self): print(self)class Child(Parent): def cprt(self): print(self)c = Child()c。cprt()c。pprt() # 等同於Child。pprt(c)p = Parent()p。pprt()

執行結果:<__main__。Child object at 0x0000000002A47080><__main__。Child object at 0x0000000002A47080><__main__。Parent object at 0x0000000002A47240>

執行c。cprt()時應該沒有理解問題,指的是Child類的例項。

但是在執行c。pprt()時,等同於Child。ppt(rc),所以self指的依然是Child類的例項,由於self中沒有定義pprt()方法,所以沿著繼承樹往上找,發現在父類Parent中定義了pprt()方法,所以就會成功呼叫。

(4)在描述符類中,self指的是描述符類的例項

class Desc: def __get__(self, ins, cls): print(‘self in Desc: %s ’ % self ) print(self, ins, cls)class Test: x = Desc() def prt(self): print(‘self in Test: %s’ % self)t = Test()t。prt()t。x

執行結果如下:self in Test: <__main__。Test object at 0x0000000002A570B8>self in Desc: <__main__。Desc object at 0x000000000283E208><__main__。Desc object at 0x000000000283E208> <__main__。Test object at 0x0000000002A570B8>

這裡主要的疑問應該在:Desc類中定義的self不是應該是呼叫它的例項t嗎?怎麼變成了Desc類的例項了呢?

因為這裡呼叫的是t.x,也就是說是Test類的例項t的屬性x,由於例項t中並沒有定義屬性x,所以找到了類屬性x,而該屬性是描述符屬性,為Desc類的例項而已,所以此處並沒有頂用Test的任何方法。

那麼我們如果直接透過類來呼叫屬性x也可以得到相同的結果。下面是把t。x改為Test。x執行的結果。

self in Test: <__main__。Test object at 0x00000000022570B8>self in Desc: <__main__。Desc object at 0x000000000223E208><__main__。Desc object at 0x000000000223E208> None

題外話:由於在很多時候描述符類中仍然需要知道呼叫該描述符的例項是誰,所以在描述符類中存在第二個引數ins(insctance),用來表示呼叫它的類例項,所以t。x時可以看到第三行中的執行結果中第二項為。而採用Test。x進行呼叫時,由於沒有例項,所以返回None。

總結

self在定義時需要定義,但是在呼叫時會自動傳入。

self的名字並不是規定死的,但是最好還是按照約定是用self

self總是指呼叫時的類的例項。

Day125:Python中self用法詳解