在介紹Python的self用法之前,先來介紹下Python中的類別和實例
我們知道,物件導向最重要的概念就是類別(class)和實例(instance),類別是抽象的模板,例如學生這個抽象的事物,可以用一個Student類別來表示。而實例是根據類別創建出來的一個個具體的“物件”,每個物件都從類別中繼承有相同的方法,但各自的資料可能不同。
1、以Student類別為例,在Python中,定義類別如下:
class Student(object): pass
(Object)表示該類別從哪個類別繼承下來的,Object類別是所有類別都會繼承的類別。
2、實例:定義好了類,就可以透過Student類別建立出Student的實例,建立實例是透過類別名稱()實作:
student = Student()
3、因為類別起到模板的作用,因此,可以在創建實例的時候,把我們認為必須綁定的屬性強制填寫進去。這裡就用到Python當中的一個內建方法__init__
方法,例如在Student類別時,把name、score等屬性綁上去:
class Student(object): def __init__(self, name, score): self.name = name self.score = score
這裡注意:(1)、__init__
方法的第一參數永遠是self
,表示創建的類別實例本身,因此,在__init__
方法內部,就可以把各種屬性綁定到self,因為self就指向創建的實例本身。 (2)、有了__init__
方法,在創建實例的時候,就不能傳入空的參數了,必須傳入與__init__
方法匹配的參數,但self不需要傳,Python解釋器會自己把實例變數傳進去:
>>>student = Student("Hugh", 99) >>>student.name "Hugh" >>>student.score 99
另外,這裡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)
>>>student = Student("Hugh", 99) >>>student.print_score Hugh: 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.__name Traceback (most recent call last): File "<stdin>", line 1, in <module> 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> <class '__main__.Test'>
从上面的例子中可以很明显的看出,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() 运行结果: <class '__main__.Test'>
(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()
运行结果:
<__main__.Child object at 0x0000000002A47080>
<__main__.Child object at 0x0000000002A47080>
<__main__.Parent object at 0x0000000002A47240>
解释:
运行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
运行结果如下:
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 <class '__main__.Test'>
以上是Python中的self怎麼使用的詳細內容。更多資訊請關注PHP中文網其他相關文章!