天天看點

Python 面向對象程式設計

類和對象

定義類

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'>