天天看點

Python中self用法詳解(轉載)

在介紹Python的self用法之前,先來介紹下Python中的類和執行個體……

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

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

class Student(object):

pass

1

2

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

2、執行個體:定義好了類,就可以通過Student類建立出Student的執行個體,建立執行個體是通過類名+()實作:

student = Student()

1

3、由于類起到模闆的作用,是以,可以在建立執行個體的時候,把我們認為必須綁定的屬性強制填寫進去。這裡就用到Python當中的一個内置方法__init__方法,例如在Student類時,把name、score等屬性綁上去:

class Student(object):

def init(self, name, score):

self.name = name

self.score = score

1

2

3

4

這裡注意:(1)、__init__方法的第一參數永遠是self,表示建立的類執行個體本身,是以,在__init__方法内部,就可以把各種屬性綁定到self,因為self就指向建立的執行個體本身。(2)、有了__init__方法,在建立執行個體的時候,就不能傳入空的參數了,必須傳入與__init__方法比對的參數,但self不需要傳,Python解釋器會自己把執行個體變量傳進去:

student = Student(“Hugh”, 99)

student.name

“Hugh”

student.score

99

1

2

3

4

5

另外,這裡self就是指類本身,self.name就是Student類的屬性變量,是Student類所有。而name是外部傳來的參數,不是Student類所自帶的。故,self.name = name的意思就是把外部傳來的參數name的值指派給Student類自己的屬性變量self.name。

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)

1

2

3

4

5

6

student = Student(“Hugh”, 99)

student.print_score

Hugh: 99

1

2

3

這樣一來,我們從外部看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)
           

1

2

3

4

5

6

7

改完後,對于外部代碼來說,沒什麼變動,但是已經無法從外部通路執行個體變量.__name和執行個體變量.__score了:

student = Student(‘Hugh’, 99)

student.__name

Traceback (most recent call last):

File “”, line 1, in

AttributeError: ‘Student’ object has no attribute ‘__name’

1

2

3

4

5

這樣就確定了外部代碼不能随意修改對象内部的狀态,這樣通過通路限制的保護,代碼更加健壯。

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

class Student(object):

def get_name(self):
    return self.__name

def get_score(self):
    return self.__score
           

1

2

3

4

5

6

7

8

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

class Student(object):

def set_score(self, score):
    self.__score = score
           

1

2

3

4

5

需要注意的是,在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’

1

2

3

4

5

6

7

8

9

同樣的,get_grade方法可以直接在執行個體變量上調用,不需要知道内部實作細節:

student.get_grade()

‘A’

1

2

6、self的仔細用法

(1)、self代表類的執行個體,而非類。

class Test:

def ppr(self):

print(self)

print(self.class)

t = Test()

t.ppr()

執行結果:

<main.Test object at 0x000000000284E080>

<class ‘main.Test’>

1

2

3

4

5

6

7

8

9

10

從上面的例子中可以很明顯的看出,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()

1

2

3

4

5

6

運作結果如下:

Traceback (most recent call last):

File “cl.py”, line 6, in

t.ppr()

TypeError: ppr() takes 0 positional arguments but 1 was given

1

2

3

4

運作時提醒錯誤如下:ppr在定義時沒有參數,但是我們運作時強行傳了一個參數。

由于上面解釋過了t.ppr()等同于Test.ppr(t),是以程式提醒我們多傳了一個參數t。

這裡實際上已經部分說明了self在定義時不可以省略。

當然,如果我們的定義和調用時均不傳類執行個體是可以的,這就是類方法。

class Test:

def ppr():

print(class)

Test.ppr()

運作結果:

<class ‘main.Test’>

1

2

3

4

5

6

7

8

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

class Parent:

def pprt(self):

print(self)

class Child(Parent):

def cprt(self):

print(self)

c = Child()

c.cprt()

c.pprt()

p = Parent()

p.pprt()

1

2

3

4

5

6

7

8

9

10

11

12

運作結果:

<main.Child object at 0x0000000002A47080>

<main.Child object at 0x0000000002A47080>

<main.Parent object at 0x0000000002A47240>

1

2

3

解釋:

運作c.cprt()時應該沒有了解問題,指的是Child類的執行個體。

但是在運作c.pprt()時,等同于Child.pprt©,是以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

1

2

3

4

5

6

7

8

9

10

11

運作結果如下:

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> <class ‘main.Test’>

1

2

3

這裡主要的疑問應該在: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 <class ‘main.Test’>

1

2

3

總結:以上是之前學習Python時的小結,現在已部落格方式呈現,同時為pyspark中調用self遇到的問題做鋪墊,後面也會對比java,未完待續…….

————————————————

版權聲明:本文為CSDN部落客「CLHugh」的原創文章,遵循 CC 4.0 BY-SA 版權協定,轉載請附上原文出處連結及本聲明。

原文連結:https://blog.csdn.net/CLHugh/article/details/75000104