Python基礎(chǔ)-類
@(Python)[python, python基礎(chǔ)]
寫在前面
如非特別說(shuō)明亿昏,下文均基于Python3
摘要
本文重點(diǎn)講述如何創(chuàng)建和使用Python
類箫攀,綁定方法與非綁定方法的區(qū)別我注,以及Python
的多態(tài)與簡(jiǎn)單繼承。
1. 面向?qū)ο缶幊?/h3>
1.1 對(duì)象和類
面向?qū)ο筮@種思想其實(shí)只是人類思維在程序設(shè)計(jì)領(lǐng)域的一種自然延伸郊闯。程序設(shè)計(jì)領(lǐng)域?qū)F(xiàn)實(shí)世界中事物自然延伸為“對(duì)象”舌涨,事物擁有其屬性和作用,對(duì)象也一樣抓半,擁有屬性以及方法喂急;復(fù)雜的面向?qū)ο蟪绦蚓褪腔谝粋€(gè)個(gè)基本的對(duì)象,相互交織笛求,構(gòu)造一個(gè)完整的對(duì)象生態(tài)廊移。
現(xiàn)實(shí)世界中“類”這個(gè)概念其實(shí)并不顯著,但是也存在探入。類在面向?qū)ο笾惺菍?duì)一個(gè)對(duì)象集合的抽象狡孔,抽象集合中對(duì)象共有的屬性,方法構(gòu)成類蜂嗽。通過(guò)類可以構(gòu)建具體的對(duì)象苗膝。
1.2 一切皆對(duì)象
Python
哲學(xué)是
一切皆對(duì)象
在使用Python
編程和學(xué)習(xí)時(shí),需時(shí)刻秉持這一思想植旧。
2. 定義Python類&對(duì)象
2.1 最簡(jiǎn)單的python類
Python
類定義的句法很簡(jiǎn)單荚醒,關(guān)鍵字class
后接合法類名即可芋类;最簡(jiǎn)單的Python
類如下:
class Person:
pass
類對(duì)象
Python
一切皆對(duì)象,類也不例外界阁。Python
解釋器在解釋完類定義這段代碼時(shí),在當(dāng)前作用域創(chuàng)建了一個(gè)類對(duì)象用來(lái)表示該類胖喳,并使用名字Person
指向該類對(duì)象泡躯;
使用類對(duì)象,可以進(jìn)行以下操作:
- 實(shí)例化
- 屬性引用
實(shí)例化&實(shí)例對(duì)象
實(shí)例化就是用類對(duì)象創(chuàng)建一個(gè)實(shí)例對(duì)象過(guò)程丽焊,實(shí)例化的結(jié)果是實(shí)例對(duì)象较剃;語(yǔ)法如下:
p = Person()
以上語(yǔ)句在當(dāng)前作用域創(chuàng)建Person
類對(duì)象的實(shí)例對(duì)象,并使用名字p
指向該對(duì)象技健。
屬性引用
引用對(duì)象的屬性非常簡(jiǎn)單写穴,使用obj.attr
的方式就可以引用對(duì)象obj
的屬性attr
。
2.2 屬性綁定
只有對(duì)象綁定過(guò)屬性之后雌贱,才能引用該屬性啊送。Python
是動(dòng)態(tài)語(yǔ)言,可以在可變對(duì)象創(chuàng)建之后為對(duì)象綁定屬性欣孤。
類對(duì)象的綁定屬性被稱為類變量馋没;實(shí)例對(duì)象的綁定屬性被稱為實(shí)例變量。通常來(lái)說(shuō)降传,實(shí)例變量是對(duì)于每個(gè)實(shí)例都獨(dú)有的數(shù)據(jù)篷朵,而類變量是該類所有實(shí)例共享的屬性和方法。
由于Python
是動(dòng)態(tài)語(yǔ)言婆排,因此可以在對(duì)象創(chuàng)建之后修改對(duì)象的信息声旺。
可以在Person
類對(duì)象創(chuàng)建之后綁定屬性:
Person.school = 'Whu University'
也可以在Person
實(shí)例對(duì)象創(chuàng)建之后綁定屬性:
p = Person()
p.name = 'Richard'
p.age = 20
def print_info(p):
print(p.name, p.age)
print_info(p)
更多關(guān)于類變量與實(shí)例變量的區(qū)別與聯(lián)系,參考 Python基礎(chǔ)-類變量和實(shí)例變量
3. 封裝
如果在對(duì)象創(chuàng)建之后再根據(jù)需要綁定屬性段只,并且在外部函數(shù)隨意訪問(wèn)實(shí)例對(duì)象的屬性腮猖,那么類機(jī)制的優(yōu)點(diǎn)就無(wú)法體現(xiàn)出來(lái)了。在實(shí)踐中翼悴,我們一般在定義類時(shí)就決定了類變量與實(shí)例變量缚够。
3.1 隱藏細(xì)節(jié)
通常來(lái)說(shuō),對(duì)象的使用者只需要關(guān)心對(duì)象能“做什么”鹦赎,而不需要也不應(yīng)該關(guān)心“怎么做”谍椅,這就是封裝了。按照封裝的思想古话,一個(gè)Python
類應(yīng)該向外提供接口雏吭,并隱藏接口實(shí)現(xiàn)細(xì)節(jié):
class Person:
school = 'Whu University' # 類屬性綁定
def __init__(self, name, age):
self.name = name # 實(shí)例屬性綁定
self.age = age # 實(shí)例屬性綁定
def print_info(self): # 暴露公共接口
print(self.name, self.age)
p = Person('Richard', 20)
p.print_info()
如上,將屬性的綁定放到類定義中陪踩,并向外提供了接口杖们。然而不幸的是悉抵,在類外部還是可以引用到類實(shí)例的屬性。
3.2 綁定方法與非綁定方法
通過(guò)直接將print_info
打印出來(lái)摘完,可以直觀看到綁定方法與非綁定方法的區(qū)別:
print(Person.print_info)
print(p.print_info)
output:
<function Person.print_info at 0x006E2AE0>
<bound method Person.print_info of <__main__.Person object at 0x006E0E10>>
可以看到類對(duì)象Person
的函數(shù)print_info
是個(gè)function
姥饰;而實(shí)例對(duì)象p
的方法print_info
是個(gè)bound method
綁定方法。
也可以查看它們的類型:
print(type(Person.print_info)) # <class 'function'>
print(type(p.print_info)) # <class 'method'>
一般來(lái)說(shuō)孝治,非綁定方法也叫函數(shù)列粪,綁定方法簡(jiǎn)稱方法。綁定方法的綁定谈飒,在于函數(shù)與特定的對(duì)象綁定在了一起岂座。
引用非數(shù)據(jù)屬性的實(shí)例屬性時(shí),會(huì)搜索它對(duì)應(yīng)的類杭措。如果名字是一個(gè)有效的函數(shù)對(duì)象费什,Python
會(huì)將實(shí)例對(duì)象連同函數(shù)對(duì)象打包到一個(gè)抽象的對(duì)象中并且依據(jù)這個(gè)對(duì)象創(chuàng)建方法對(duì)象:這就是被調(diào)用的方法對(duì)象(綁定方法)。當(dāng)使用參數(shù)列表調(diào)用方法對(duì)象時(shí)手素,會(huì)使用實(shí)例對(duì)象以及原有參數(shù)列表構(gòu)建新的參數(shù)列表鸳址,并且使用新的參數(shù)列表調(diào)用函數(shù)對(duì)象。
那么刑桑,以綁定形式調(diào)用方法時(shí)氯质,第一個(gè)參數(shù)不是顯式傳遞的,而是解釋器隱式傳遞的:
p.print_info()
同樣祠斧,也可以通過(guò)調(diào)用綁定方法的函數(shù)版本達(dá)到相同的目的:
Person.print_info(p) # 函數(shù)必須顯式傳遞參數(shù)
3.3 魔法方法__init__
方法__init__
是一個(gè)充滿魔力的方法闻察,一般以雙下劃線開頭并且結(jié)尾的方法對(duì)于Python
都有特殊的意義,在滿足條件的時(shí)候被調(diào)用琢锋。
__init__
就是一個(gè)構(gòu)造方法辕漂,在類對(duì)象的實(shí)例化過(guò)程中被調(diào)用,即在p = Person()
這條語(yǔ)句中吴超,Python
解釋器自動(dòng)調(diào)用了__init__
方法钉嘹。
一般來(lái)說(shuō),使用__init__
方法來(lái)初始化實(shí)例對(duì)象鲸阻,即為實(shí)例對(duì)象綁定屬性跋涣。
3.4 綁定方法參數(shù)self
注意到類Person
定義的兩個(gè)方法中都有一個(gè)占據(jù)第一個(gè)參數(shù)位置,名為self
的參數(shù)鸟悴。其實(shí)這是一個(gè)特殊參數(shù):當(dāng)調(diào)用綁定方法時(shí)陈辱,調(diào)用實(shí)例對(duì)象會(huì)被Python
解釋器作為第一個(gè)參數(shù)來(lái)調(diào)用綁定方法。
因此细诸,重要的是參數(shù)位置沛贪,在綁定方法中,第一個(gè)參數(shù)總是代表當(dāng)前調(diào)用實(shí)例對(duì)象,與參數(shù)名字無(wú)關(guān)利赋。但是水评,self
的字面意思是自身的意思,這個(gè)名字能很好的體現(xiàn)第一個(gè)參數(shù)的實(shí)際意義媚送。
在類中中燥,類的局部作用域與函數(shù)的局部作用域是不能相互訪問(wèn)的,詳見:Python進(jìn)階 - 命名空間與作用域季希。因此褪那,函數(shù)直接只能以對(duì)象引用的方式訪問(wèn)其他屬性,這也是對(duì)外提供的接口都有self
參數(shù)的原因式塌。
class Person:
school = 'Whu University'
def __init__(self, name, age):
self.name = name
self.age = age
# print(school) 類屬性school是不能直接訪問(wèn)的,需要用Person.school的形式
def print_info(self): # 如果沒(méi)有self友浸,將訪問(wèn)不到name和age
print(self.name, self.age)
3.5 私有化
Python
并沒(méi)有語(yǔ)法支持屬性的私有化峰尝,但是Python
可以使用“名稱變化術(shù)”改變私有屬性的名字:
class Person:
__school = 'Whu University'
def __init__(self, name, age):
self.__name = name
self.__age = age
def print_info(self):
print(self.__name, self.__age)
p = Person('Richard', 20)
p.print_info()
print(p.__dict__) # {'_Person__name': 'Richard', '_Person__age': 20}
通常來(lái)說(shuō),Python
解釋器會(huì)將類定義中所有以__
開頭的屬性變名收恢,具體變名的規(guī)則由解釋器決定武学,目前的CPython
解釋器是在前面加_類名
的方式。
誠(chéng)然伦意,仍然可以使用變名后的屬性名火窒,如此處的_Person__name, _Person__age
來(lái)訪問(wèn)實(shí)例對(duì)象的屬性,但是通常不建議這么做驮肉。因?yàn)樽兠囊?guī)則是解釋器決定的熏矿,如果變更解釋器,變名可能就不一樣了离钝;再則票编,變名是類作者給類使用者的一個(gè)強(qiáng)力信號(hào),不建議使用者直接訪問(wèn)屬性卵渴。
一般地慧域,還可以在屬性名前加前綴_
,如_name, _age
浪读,來(lái)表達(dá)私有屬性昔榴。這種方式不會(huì)引起解釋器的變名,但是發(fā)出了不應(yīng)該直接訪問(wèn)這些屬性的信號(hào)碘橘。同時(shí)互订,這種規(guī)則的屬性不會(huì)被from module import *
的方式導(dǎo)入。
4. 多態(tài)
多態(tài)意味著就算不知道變量所引用的對(duì)象的類型蛹屿,還是能夠?qū)λM(jìn)行操作屁奏,而它會(huì)根據(jù)對(duì)象的實(shí)際類型的不同表現(xiàn)出不同的行為。
4.1 其他語(yǔ)言的多態(tài)
在一些高級(jí)語(yǔ)言中错负,為實(shí)現(xiàn)多態(tài)坟瓢。首先會(huì)定義一個(gè)接口勇边,然后實(shí)現(xiàn)若干繼承接口的的具體類,以頂層的接口來(lái)引用具體的接口實(shí)現(xiàn)折联,在運(yùn)行時(shí)根據(jù)具體實(shí)現(xiàn)的不同表現(xiàn)出不同的行為:
interface Top {
void bar();
}
class A implements Top {
public void bar(){
// implementing of A
}
}
class B implements Top {
public void bar(){
// implementing of B
}
}
void foo(Top t) {
t.bar()
}
這里foo
方法根據(jù)參數(shù)t
實(shí)際類型的不同會(huì)表現(xiàn)出不同的行為粒褒。
4.2 Python的多態(tài)
Python
的多態(tài)思想與上面是相同的,但是在實(shí)現(xiàn)上有所不同诚镰。Python中的多態(tài)是基于對(duì)象的行為奕坟,而不像其他語(yǔ)言是基于父類或者接口。Python
雖然是強(qiáng)類型語(yǔ)言清笨,但是Python
的變量是沒(méi)有類型的月杉,因此Python
多態(tài)不需要一個(gè)頂層的接口。只需要實(shí)際的對(duì)象擁有需要的屬性即可:
class FooLike1:
def wow(self):
print('foo in FooLike1')
class FooLike2:
def wow(self):
print('foo in FooLike2')
def bar(foo):
foo.wow()
bar(FooLike1()) # foo in FooLike1
bar(FooLike2()) # foo in FooLike2
當(dāng)然參與多態(tài)的對(duì)象有共同父類也是可以的抠艾,重點(diǎn)是都有參與多態(tài)需要的函數(shù)苛萎。Python
中的多態(tài)要靈活得多;只要對(duì)象擁有指定方法检号,就可以參與多態(tài)腌歉,這種對(duì)象成為“like”對(duì)象,如和file
對(duì)象擁有read
方法的對(duì)象是file like
對(duì)象齐苛,可以參與到關(guān)于read
的多態(tài)中翘盖。
5. 繼承
繼承是一個(gè)懶惰的行為,子類可以不勞而獲從父類獲取必要信息凹蜂。如現(xiàn)在有一個(gè)Person
類馍驯,擁有名字和年齡屬性,現(xiàn)在要?jiǎng)?chuàng)建一個(gè)Student
類炊甲,也有名字和年齡泥彤,并且增加和學(xué)號(hào)信息;通過(guò)繼承卿啡,可以從Person
不勞而獲一些信息吟吝。
5.1 如何繼承
Python
的繼承語(yǔ)法也很簡(jiǎn)單,只需要在類名后跟括號(hào)颈娜,括號(hào)中寫入要繼承的類即可:
class DerivedClassName(BaseClassName):
<statement-1>
.
.
.
<statement-N>
當(dāng)然剑逃,如果是多繼承,在繼承列表里添加即可:
class DerivedClassName(BaseClassName1, BaseClassName2, ..., BaseClassNamen):
<statement-1>
.
.
.
<statement-N>
5.1 繼承到了什么
那么官辽,子類到底從父類繼承到了什么呢蛹磺?一個(gè)直觀的例子可以看出:
class Person:
__school = 'Whu University'
def __init__(self, name, age):
self.__name = name
self.__age = age
def print_info(self):
print(self.__name, self.__age)
def test():
pass
class Student(Person):
def __init__(self):
pass
print(dir(Student))
s = Student()
print(dir(s))
output:
['_Person__school', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'print_info', 'test']
=======分割線=======
['_Person__school', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'print_info', 'test']
可以看出,子類類對(duì)象繼承到了父類類對(duì)象的所有東西同仆,包括類屬性萤捆,函數(shù)。但是子類實(shí)例對(duì)象沒(méi)有繼承到父類實(shí)力對(duì)象的實(shí)例屬性,即 __name
和__age
沒(méi)有繼承到俗或。
我們說(shuō)過(guò)市怎,屬性只有綁定之后才能引用,顯然辛慰,在子類中沒(méi)有調(diào)用父類的__init__
方法区匠,父類的實(shí)例對(duì)象的屬性自然沒(méi)有初始化;因此只要在子類中調(diào)用父類的構(gòu)造方法帅腌,就可以繼承到實(shí)例屬性了:
class Student(Person):
def __init__(self, name, age, stu_id):
self.stu_id = stu_id
super().__init__(name, age)
子類如何初始化父類是一個(gè)大問(wèn)題驰弄,單繼承比較簡(jiǎn)單,涉及到多繼承的初始化就比較復(fù)雜了速客。
5.2 獲取繼承關(guān)系
Python有兩個(gè)可以判斷繼承關(guān)系的內(nèi)建函數(shù):
- 使用isinstance()檢查實(shí)例的類型:isinstance(obj, int)戚篙,當(dāng)且僅當(dāng)obj.class是int或者派生與int的類時(shí),返回True
- 使用issubclass()檢查類的繼承關(guān)系:issubclass(bool, int)返回True溺职,因?yàn)閎ool是int的子類已球。然而issubclass(float, int)返回False,因?yàn)閒loat不是int的子類辅愿。