Python中self用法詳解

在介紹Python的self用法以前,先來介紹下Python中的類和實例……
咱們知道,面向對象最重要的概念就是類(class)和實例(instance),類是抽象的模板,好比學生這個抽象的事物,能夠用一個Student類來表示。而實例是根據類建立出來的一個個具體的「對象」,每個對象都從類中繼承有相同的方法,但各自的數據可能不一樣。
一、以Student類爲例,在Python中,定義類以下:java

class Student(object):函數

pass

1
2
(Object)表示該類從哪一個類繼承下來的,Object類是全部類都會繼承的類。學習

二、實例:定義好了類,就能夠經過Student類建立出Student的實例,建立實例是經過類名+()實現:this

student = Student()
1
三、因爲類起到模板的做用,所以,能夠在建立實例的時候,把咱們認爲必須綁定的屬性強制填寫進去。這裏就用到Python當中的一個內置方法__init__方法,例如在Student類時,把name、score等屬性綁上去:spa

class Student(object):code

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。

四、和普通數相比,在類中定義函數只有一點不一樣,就是第一參數永遠是類的自己實例變量self,而且調用時,不用傳遞該參數。除此以外,類的方法(函數)和普通函數沒啥區別,你既能夠用默認參數、可變參數或者關鍵字參數(args是可變參數,args接收的是一個tuple,*kw是關鍵字參數,kw接收的是一個dict)。對象

五、既然Student類實例自己就擁有這些數據,那麼要訪問這些數據,就不必從外面的函數去訪問,而能夠直接在Student類的內部定義訪問數據的函數(方法),這樣,就能夠把」數據」封裝起來。這些封裝數據的函數是和Student類自己是關聯起來的,稱之爲類的方法:繼承

class Student(obiect):get

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 "<stdin>", line 1, in <module>
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
六、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 <module>

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(c),因此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的任何方法。 若是你在學習Python的過程當中碰見了不少疑問和難題,能夠加-q-u-n 227 -435-450裏面有軟件視頻資料免費

那麼咱們若是直接經過類來調用屬性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'>123總結:以上是以前學習Python時的小結,,同時爲pyspark中調用self遇到的問題作鋪墊,後面也會對比java,未完待續…….

相關文章
相關標籤/搜索