類和對象
定義類
Python支持面向?qū)ο缶幊唐担旅媸且粋€例子夺欲。我們可以看到毯盈,在Python中聲明類和其他語言差不多剃毒。不過實際上差別還是挺大的。
首先搂赋,Python沒有嚴格意義上的構(gòu)造函數(shù)赘阀,只有一個__init__(self,XXX)
函數(shù),該函數(shù)和構(gòu)造函數(shù)的功能差不多脑奠,用來初始化對象的狀態(tài)基公。之后創(chuàng)建對象的時候,直接使用類名和參數(shù)列表來創(chuàng)建宋欺,這樣會調(diào)用初始化函數(shù)來創(chuàng)建對象轰豆。
特別要提一點,所有的Python類的實例函數(shù)的第一個參數(shù)必須是self
齿诞,這個參數(shù)類似于Java的this
關(guān)鍵字酸休,指代當前對象。如果我們調(diào)用類上的方法a.f()
祷杈,那么a
這個實例就會傳遞給self
參數(shù)斑司。
下面介紹一下Python的實例字段。實例字段使用self.XXX
來定義但汞。Python不能像Java那樣靜態(tài)的聲明字段宿刮。如果有需要,直接使用self.
加字段名即可私蕾,這也是動態(tài)語言的一個特點僵缺。
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self):
return f'Person(name:{self.name},age:{self.age})'
person=Person('yitian',24)
print(person)
上面這個例子還定義了一個__str__(self)
函數(shù),這個函數(shù)和Java的toString()
函數(shù)類似踩叭,當輸出的時候就會自動調(diào)用這個函數(shù)將對象轉(zhuǎn)換為可讀的字符串形式磕潮。Python中還有很多__XXX__
形式的慣例函數(shù),肩負著不同的職責容贝。
共享字段和私有字段
首先需要說明自脯,Python中沒有public
、private
這樣的字段訪問修飾符嗤疯,也就是說只要你想冤今,你可以調(diào)用對象上的任意字段和方法闺兢。這里說的都只是一種編碼的契約茂缚,我們在編寫Python類的時候也要遵循這些契約戏罢,才能寫出合格的代碼來。
前面已經(jīng)說了脚囊,實例字段使用self.
來訪問龟糕。如果在類中編寫沒有self
的變量,那么這些變量就是類變量悔耘,可以在該類的所有對象之間共享讲岁。這個概念類似Java的靜態(tài)字段。下面的population
就是一個共享字段的例子衬以。
class Person:
population = 0
def __init__(self, name, age):
self.name = name
self.age = age
Person.population += 1
def __str__(self):
return f'Person(name:{self.name},age:{self.age})'
yitian = Person('yitian', 24)
zhang3 = Person('zhang3', 25)
print(yitian)
print(f'population:{Person.population}')
最后來說說私有字段缓艳。私有字段慣例上需要添加下劃線_
前綴。雖然這些“私有變量”也可以在類外邊訪問看峻,但是我們千萬不要這么做阶淘。私有字段作為類的內(nèi)部實現(xiàn),隨時可能存在變化的可能互妓,不應(yīng)該向外部暴露溪窒。我們的代碼中也不應(yīng)該依賴其他類庫的私有變量。
結(jié)構(gòu)體
有時候我們可能需要結(jié)構(gòu)體或者數(shù)據(jù)類這一概念冯勉,也就是將相關(guān)的變量封裝到一個類中澈蚌。在Python中可以定義一個空類,然后創(chuàng)建對象灼狰,并動態(tài)賦值宛瞄。
print('--------------結(jié)構(gòu)體--------------')
class StudentInfo:
pass
info = StudentInfo()
info.name = 'yitian'
info.age = 24
info.gender = 'male'
print(f'info({info.name},{info.age},{info.gender})')
繼承
單繼承
支持定義類的語言一般也都支持繼承,不然要這么個功能有什么用伏嗜。如果要繼承某個或某些類坛悉,在類定義上使用括號指定要繼承的基類。如果需要訪問基類的成員承绸,使用基類名
加點訪問符.
來訪問裸影。
class Student(Person):
def __init__(self, id, name, age):
Person.__init__(self, name, age)
self.id = id
def __str__(self):
return f'Student(id:{self.id},name:{self.name},age:{self.age})'
def introduce_myself(self):
print(f"I'm {self.name}, I'm {self.age} years old student.")
xiaoming = Student(1, 'xiaoming', 14)
print(xiaoming)
xiaoming.introduce_myself()
繼承層次
按照C++的概念,Python類的所有函數(shù)都是虛的军熏,也就是說在子類中重寫的所有函數(shù)轩猩,都會父類的同名函數(shù)。如果需要調(diào)用父類的版本荡澎,需要使用父類名.XXX
的方式來訪問均践。例如,如果我們要訪問xiaoming的父類自我介紹摩幔。就需要使用下面的語句彤委。
# 調(diào)用父類版本
Person.introduce_myself(xiaoming)
Python提供了兩個內(nèi)置函數(shù)isinstance
和issubclass
來幫我們判斷類的繼承關(guān)系。用法很簡單或衡,下面的結(jié)果依次是:真真真假焦影。
print('--------------繼承關(guān)系--------------')
print(f'xiaoming is Student:{isinstance(xiaoming,Student)}')
print(f'xiaoming is Person:{isinstance(xiaoming,Person)}')
print(f'Student is Person:{issubclass(Student,Person)}')
print(f'Person is Student:{issubclass(Person,Student)}')
多重繼承
最后再來說說多重繼承车遂。多重繼承的類簽名類似下面這樣。當我們訪問子類的成員時斯辰,Python會先查找子類中存不存在該成員舶担。如果不存在的話在查找父類,如果父類不存在就查找父類的父類彬呻,直到查到頭為止衣陶。如果到這時候還沒查找到就會拋出異常。
對于多重繼承的話闸氮,這個過程可以簡單的看成從左到右的剪况、深度優(yōu)先的查找過程:如果子類中不存在該成員,就先從Base1開始查找蒲跨,如果Base1和它的所有父類都沒有拯欧,再從Base2開始查找,以此類推财骨。當然實際情況略微復(fù)雜一點镐作,因為Python會檢查類繼承層次是否存在相同的父類,并確保相同的父類只訪問一次隆箩。
class DerivedClassName(Base1, Base2, Base3):
迭代器和生成器
迭代器
在很多編程語言中都有迭代器的概念该贾,迭代器可以在for-loop
循環(huán)中使用。一般情況下迭代器會有next()
和hasNext()
等類似的方法捌臊,確定什么時候應(yīng)該停止迭代杨蛋,什么時候返回元素。
在Python中需要使用__next__(self)
函數(shù)來執(zhí)行迭代理澎,如果到了末尾則需要拋出StopIteration
異常逞力。如果編寫了__next__(self)
函數(shù),我們就可以讓__iter__(self):
函數(shù)返回自身糠爬。這樣一個迭代器就寫好了寇荧,我們可以在for循環(huán)等地方使用了。
print('--------------迭代器--------------')
class IterableObj:
def __init__(self, data):
self.data = data
self.index = -1
def __iter__(self):
return self
def __next__(self):
if self.index == len(self.data) - 1:
raise StopIteration
self.index += 1
return self.data[self.index]
obj1 = IterableObj([1, 2, 3])
for i in obj1:
print(i, end=' ')
print()
Python還包含了兩個內(nèi)置函數(shù)iter()
和next()
用于創(chuàng)建迭代器和執(zhí)行迭代执隧。下面是使用列表迭代的例子揩抡。
list1 = [1, 2, 3]
iter1 = iter(list1)
e1 = next(iter1)
e2 = next(iter1)
e3 = next(iter1)
print('List:', e1, e2, e3)
生成器
迭代器雖然使用比較簡單,但還是挺麻煩的镀琉。我們可以使用生成器更簡單的創(chuàng)建迭代器峦嗤。生成器其實就是一個函數(shù),不過這個函數(shù)比較特殊屋摔,它不使用return
返回結(jié)果烁设,而是使用yield
返回一系列值。當我們在循環(huán)中或者使用next()
函數(shù)調(diào)用生成器的時候钓试,每次調(diào)用生成器都會使用yield
返回一個值装黑。
print('--------------生成器--------------')
def even_generator(data):
index = 0
while index < len(data):
if data[index] % 2 == 0:
yield data[index]
index += 1
integer_list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
print(f'even_generator:{[i for i in even_generator(integer_list)]}')
從這個例子我們可以看到耙替,生成器確實比迭代器更方便。
生成器表達式
生成器表達式其實和列表解析表達式差不多曹体,只不過列表解析表達式使用方括號,而生成器表達式使用小括號硝烂。另外箕别,生成器表達式返回的是一個生成器,而列表解析表達式返回的是列表滞谢。除此之外串稀,它們在迭代的時候結(jié)果完全相同。
不過狮杨,由于生成器不是一次性生成所有值母截,所以當?shù)男蛄蟹浅4蟮臅r候,最好使用生成器表達式而不是列表解析表達式橄教。
print('--------------生成器表達式--------------')
odd_generator = (i for i in range(1, 11) if i % 2 != 0)
odd_list = [i for i in range(1, 11) if i % 2 != 0]
print(f'generator type:{type(odd_generator)}')
print(f'list type:{type(odd_list)}')
結(jié)果如下清寇。
--------------生成器表達式--------------
generator type:<class 'generator'>
list type:<class 'list'>