類和對象
定義類
Python支持面向對象編程,下面是一個例子。我們可以看到,在Python中聲明類和其他語言差不多。不過實際上差別還是挺大的。
首先,Python沒有嚴格意義上的構造函數,只有一個__init__(self,XXX)
函數,該函數和構造函數的功能差不多,用來初始化對象的狀態。之后創建對象的時候,直接使用類名和參數列表來創建,這樣會調用初始化函數來創建對象。
特別要提一點,所有的Python類的實例函數的第一個參數必須是self
,這個參數類似于Java的this
關鍵字,指代當前對象。如果我們調用類上的方法a.f()
,那么a
這個實例就會傳遞給self
參數。
下面介紹一下Python的實例字段。實例字段使用self.XXX
來定義。Python不能像Java那樣靜態的聲明字段。如果有需要,直接使用self.
加字段名即可,這也是動態語言的一個特點。
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)
函數,這個函數和Java的toString()
函數類似,當輸出的時候就會自動調用這個函數將對象轉換為可讀的字符串形式。Python中還有很多__XXX__
形式的慣例函數,肩負著不同的職責。
共享字段和私有字段
首先需要說明,Python中沒有public
、private
這樣的字段訪問修飾符,也就是說只要你想,你可以調用對象上的任意字段和方法。這里說的都只是一種編碼的契約,我們在編寫Python類的時候也要遵循這些契約,才能寫出合格的代碼來。
前面已經說了,實例字段使用self.
來訪問。如果在類中編寫沒有self
的變量,那么這些變量就是類變量,可以在該類的所有對象之間共享。這個概念類似Java的靜態字段。下面的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}')
最后來說說私有字段。私有字段慣例上需要添加下劃線_
前綴。雖然這些“私有變量”也可以在類外邊訪問,但是我們千萬不要這么做。私有字段作為類的內部實現,隨時可能存在變化的可能,不應該向外部暴露。我們的代碼中也不應該依賴其他類庫的私有變量。
結構體
有時候我們可能需要結構體或者數據類這一概念,也就是將相關的變量封裝到一個類中。在Python中可以定義一個空類,然后創建對象,并動態賦值。
print('--------------結構體--------------')
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類的所有函數都是虛的,也就是說在子類中重寫的所有函數,都會父類的同名函數。如果需要調用父類的版本,需要使用父類名.XXX
的方式來訪問。例如,如果我們要訪問xiaoming的父類自我介紹。就需要使用下面的語句。
# 調用父類版本
Person.introduce_myself(xiaoming)
Python提供了兩個內置函數isinstance
和issubclass
來幫我們判斷類的繼承關系。用法很簡單,下面的結果依次是:真真真假。
print('--------------繼承關系--------------')
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會先查找子類中存不存在該成員。如果不存在的話在查找父類,如果父類不存在就查找父類的父類,直到查到頭為止。如果到這時候還沒查找到就會拋出異常。
對于多重繼承的話,這個過程可以簡單的看成從左到右的、深度優先的查找過程:如果子類中不存在該成員,就先從Base1開始查找,如果Base1和它的所有父類都沒有,再從Base2開始查找,以此類推。當然實際情況略微復雜一點,因為Python會檢查類繼承層次是否存在相同的父類,并確保相同的父類只訪問一次。
class DerivedClassName(Base1, Base2, Base3):
迭代器和生成器
迭代器
在很多編程語言中都有迭代器的概念,迭代器可以在for-loop
循環中使用。一般情況下迭代器會有next()
和hasNext()
等類似的方法,確定什么時候應該停止迭代,什么時候返回元素。
在Python中需要使用__next__(self)
函數來執行迭代,如果到了末尾則需要拋出StopIteration
異常。如果編寫了__next__(self)
函數,我們就可以讓__iter__(self):
函數返回自身。這樣一個迭代器就寫好了,我們可以在for循環等地方使用了。
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還包含了兩個內置函數iter()
和next()
用于創建迭代器和執行迭代。下面是使用列表迭代的例子。
list1 = [1, 2, 3]
iter1 = iter(list1)
e1 = next(iter1)
e2 = next(iter1)
e3 = next(iter1)
print('List:', e1, e2, e3)
生成器
迭代器雖然使用比較簡單,但還是挺麻煩的。我們可以使用生成器更簡單的創建迭代器。生成器其實就是一個函數,不過這個函數比較特殊,它不使用return
返回結果,而是使用yield
返回一系列值。當我們在循環中或者使用next()
函數調用生成器的時候,每次調用生成器都會使用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)]}')
從這個例子我們可以看到,生成器確實比迭代器更方便。
生成器表達式
生成器表達式其實和列表解析表達式差不多,只不過列表解析表達式使用方括號,而生成器表達式使用小括號。另外,生成器表達式返回的是一個生成器,而列表解析表達式返回的是列表。除此之外,它們在迭代的時候結果完全相同。
不過,由于生成器不是一次性生成所有值,所以當迭代的序列非常大的時候,最好使用生成器表達式而不是列表解析表達式。
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)}')
結果如下。
--------------生成器表達式--------------
generator type:<class 'generator'>
list type:<class 'list'>