#4 Python面向對象(三)

前言

前兩節講解了Python面向對象的思想和Python類中各種變量的含義以及區別。肯定有小夥伴會問,類初始化時是否可以傳入參數?如果有多個類中含有共同的函數方法,是否可以重複利用?本節就帶着這些問題來繼續深入類。Here We Go!

一、類的傳參

1.1 帶參數的初始化

還是以Doctor類為例,假如lisi是一位男性醫生,現在要求在生成lisi這個對象時傳入其性別。怎麼辦呢?

按照之前所學的,類似於函數的傳參,肯定是:

lisi = Doctor('male')

的確,類帶參數的初始化就是這麼用的,那麼再按照函數的思想,類源代碼應該這麼寫:

class Doctor(name):
    def talk(self):
        print('My gender is {0}'.format(self.name))


lisi = Doctor('male')

lisi.talk()
# 按照函數的思想,創建一個帶參數的類
# 這是錯誤的!!!!

你已經看到,代碼里已經標出這是錯誤的編寫方法,不信的話來運行一下:

Traceback (most recent call last):
  File "1.py", line 1, in <module>
    class Doctor(name):
NameError: name 'name' is not defined


# 竟然拋出name未定義異常

這是為什麼呢?莫名其妙的竟然拋出未定義異常,關於這個問題將會放到下面(類的性質)來講

那帶參數的類要怎麼編寫呢?使用特殊方法 __init__(注意:init左右兩邊都是兩個下劃線) ,先來看個例子:

class Doctor():
    def __init__(self, name):
    ¦   self.name = name

    def talk(self):
    ¦   print('My gender is {0}'.format(self.name))


lisi = Doctor('male')

lisi.talk()

# 運行結果:
My gender is male

通過上面的代碼可以看出,要想創建一個帶參數的類,類裏面要創建一個__init__的方法,其實這個特殊的函數叫做構造函數,構造函數會在實例化類后默默運行一次,也就是說,只要實例化了一個類,那麼就已經運行了構造函數。構造函數通常用來傳入參數使用。以上就是類的傳參。

1.2 類實例化后運行順序

當一個類實例化后,類中的代碼被首先運行,其次是構造函數里的代碼,再然後是被調用的函數被運行,最後是析構函數被運行(析構函數將放在類的特殊方法講)

class Doctor():
    def __init__(self, name):
    ¦   print('我是第二個被運行的')
    ¦   self.name = name

    print('先運行我')

    def talk(self):
    ¦   print('只有調用我時才運行我')


lisi = Doctor('male')

lisi.talk()


# 運行結果:
先運行我
我是第二個被運行的
只有調用我時才運行我

從上面的代碼中可以清晰的看到每個代碼塊的運行順序

二、面向對象的性質

面向對象共有三大性質:封裝性、繼承性、多態性。這三大性質是一定要記住的,不僅要記住,更要理解它們👹

2.1 封裝性

先舉個栗子🌰哇,一所學校有教學樓、圖書館、宿舍樓,你作為這個學校的學生,你當然可以隨意使用這三個地點,但是外來人員恐怕就不能使用了,學校阻止外來人員的辦法是建立圍牆,將學校圍起來。這就是封裝的含義,建立圍牆圍學校(類)就是封裝,只有學生(類的對象)可以使用學校資源,這兩點加起來就是封裝性。

封裝性概念:把客觀事物封裝成抽象的類,並且類可以把自己的數據和方法只讓可信的類或者對象操作,對不可信的進行信息隱藏。對內部數據進行了保護,防止程序其他部分使用或修改內部數據

總的來說,封裝性就是安全➕自私🤭

2.2 繼承性

還記得前言中帶的問題嗎?當多個類擁有同樣的方法時,是否可以只寫一次重複利用,這就是繼承的優勢。

繼承繼承,顧名思義,就是繼承🤥,父親和兒子嘛

繼承性:它可以使用現有類的所有功能,並在無需重新編寫原來的類的情況下對這些功能進行擴展。

繼承者稱為:子類、派生類;被繼承者稱為:基類、父類、超類;在Python中,被繼承者更習慣稱為超類

說了這麼多枯燥難以理解的概念,其實只需要一些例子就夠了:

class Animal:
    '''
    所有動物的超類
    '''
    def eat(self):
    ¦   print('動物都會吃飯')

    def talk(self):
    ¦   print('動物都會叫')


class Dog(Animal):
    '''
    繼承Animal
    '''
    def smell(self):
    ¦   print('狗的嗅覺靈敏')


class Cat(Animal):
    '''
    繼承Animal
    '''
    def tree(self):
    ¦   print('貓會爬樹')

從上面的代碼可以看到,原來 類(超類) 括號裏面時超類啊,不是指參數。調用的例子:

dog = Dog()  # 實例化dog
cat = Cat()  # 實例化cat

dog.eat()  # dog調用了超類Animal的方法
dog.talk()
dog.smell()  # dog調用自己的smell方法

cat.eat()  # cat也是一樣的
cat.talk()
cat.tree()
# 運行結果:
動物都會吃飯
動物都會叫
狗的嗅覺靈敏
動物都會吃飯
動物都會叫
貓會爬樹

通過繼承,可以大大增加代碼的利用率。

人類社會中,繼承往往是長江後浪推前浪,一浪更比一浪強!Python中的子類當然也可以將超類拍死在沙灘上🥴,那就是改寫超類方法方便自己

2.2.1 繼承性——改寫不帶參數的超類

class Animal:
    '''
    所有動物的超類
    '''

    def eat(self):
    ¦   print('動物都會吃飯')

    def talk(self):
    ¦   print('動物都會叫')


class Dog(Animal):
    '''
    繼承Animal
    '''

    def smell(self):
    ¦   print('狗的嗅覺靈敏')

    def eat(self):
    ¦   '''
    ¦   改寫超類eat方法
    ¦   '''
    ¦   print('吃肉!')


dog = Dog()  # 實例化dog

dog.talk()
dog.smell()  # dog調用自己的smell方法
dog.eat()  # 調用被改寫的eat方法
# 運行結果:
動物都會叫
狗的嗅覺靈敏
吃肉!

2.2.2 繼承性——改寫帶參數的超類

超類帶參數,子類不帶參數

    '''
    所有動物的超類
    '''

    def __init__(self, name, age):
    ¦   self.name = name
    ¦   self.age = age

    def eat(self):
    ¦   print('動物都會吃飯')

    def talk(self):
    ¦   print('動物都會叫')


class Dog(Animal):
    '''
    繼承Animal
    '''

    def smell(self):
    ¦   print('狗的嗅覺靈敏')

    def eat(self):
    ¦   '''
    ¦   改寫超類eat方法
    ¦   '''
    ¦   print('吃肉!')


dog = Dog()  # 實例化dog

dog.eat()

# 運行結果:

Traceback (most recent call last):
File “6.py”, line 32, in <module>
dog = Dog() # 實例化dog
TypeError: __init__() missing 2 required positional arguments: ‘name’ and ‘age’

運行上述代碼,會拋出name,age未定義異常,也就是說,當超類帶有參數時,子類實例化時也要參入超類的參數:

dog = Dog('wangwang',5)  # 實例化dog,傳入超類需要的參數

dog.eat()


# 運行結果:
吃肉!

 超類帶參數,子類帶參數

class Animal:
    '''
    所有動物的超類
    '''

    def __init__(self, name, age):
    ¦   self.name = name
    ¦   self.age = age

    def eat(self):
    ¦   print('動物都會吃飯')

    def talk(self):
    ¦   print('動物都會叫')


class Dog(Animal):
    '''
    繼承Animal
    '''

    def __init__(self, gender):
    ¦   self.gender = gender

    def smell(self):
    ¦   print('狗的嗅覺靈敏')

    def eat(self):
    ¦   '''
    ¦   改寫超類eat方法
    ¦   '''
    ¦   print('吃肉!')

看到這裏,你肯定會有疑問🤔️,傳入幾個參數才會正確呢?超類需要兩個,子類需要一個,那就是三個嘍~~~(天真🙂)

dog = Dog('wangwang',5, 'male')  # 實例化dog

dog.eat()


# 運行結果:
Traceback (most recent call last):
  File "7.py", line 35, in <module>
    dog = Dog('wangwang',5, 'male')  # 實例化dog
TypeError: __init__() takes 2 positional arguments but 4 were given

根據異常信息可知,__init__()需要兩個參數,但是給了4個。

?????哪來的4個?????明明只傳入了3個

不管幾個了,既然需要兩個參數,那就是只用傳入超類的參數啦~~~(天真🙂)

dog = Dog('wangwang',5)  # 實例化dog

dog.eat()


# 運行結果:
Traceback (most recent call last):
  File "7.py", line 35, in <module>
    dog = Dog('wangwang',5)  # 實例化dog
TypeError: __init__() takes 2 positional arguments but 3 were given

根據異常信息可知,__init__()需要2個參數,但是給了3個。

?????怎麼又成3個了?????

難不成傳入1個會變成2個?豈不是和2個參數對應了(嘿嘿🙊)

dog = Dog('wangwang')  # 實例化dog

dog.eat()


# 運行結果:
吃肉!

終於正確了,那麼傳入的這個參數到底是哪個變量呢?

dog = Dog('wangwang')  # 實例化dog
print(dog.name)
print(dog.age)
print(dog.gender)

# 運行結果:
Traceback (most recent call last):
  File "7.py", line 36, in <module>
    print(dog.name)
AttributeError: 'Dog' object has no attribute 'name'
# 沒有name變量
# 這時應該已經猜出傳入的參數給gender變量了
dog = Dog('wangwang')  # 實例化dog
print(dog.gender)
dog.eat()


# 運行結果:
wangwang
吃肉!

可以看到,的確是傳給了gender變量了,那麼為什麼只需要一個參數呢?超類的兩個參數不翼而飛?其實如果你理解了上一點中的重構方法,就會發現上面的代碼中根本就是重構了超類了__init__()方法,已經和超類中的__init__()無關了🤯

那如果超類的參數和子類的參數都需要呢?Python3中,使用super()方法將超類的構造函數複製到子類中,用法如下:

class Animal:
    '''
    所有動物的超類
    '''

    def __init__(self, name, age):
        self.name = name
        self.age = age

    def eat(self):
        print('動物都會吃飯')

    def talk(self):
        print('動物都會叫')


class Dog(Animal):
    '''
    繼承Animal
    '''

    def __init__(self, name, age, gender):
        super().__init__(name, age)  # 使用super方法
        self.gender = gender

    def smell(self):
        print('狗的嗅覺靈敏')

    def eat(self):
        '''
        改寫超類eat方法
        '''
        print('吃肉!')


dog = Dog('wangwang', 5, 'male')  # 實例化dog
print(dog.name)
print(dog.age)
print(dog.gender)
dog.eat()

# 運行結果:
wangwang
5
male
吃肉!

從上面代碼中可以看到super()其實是超類的一個對象,上述代碼的思想是:給子類傳入3個參數,前2個參數再傳給超類,從而將超類需要的參數傳入

2.3 多態性

什麼是多態呢?先舉個例子哇:今天班級大掃除,老師要分配工作,現有1~10共10個學生,老師肯定會這樣說:“1,2,3,4,5去掃地,6,7,8,9,10拖地”,絕對不會這樣說:“1去掃地,2去掃地,3去掃地,4去掃地,5去掃地,6去拖地,7去拖地,8去拖地,9去拖地,10去拖地”。這其實就是多態性,掃地是一個接口,多個人共用,拖地時一個接口,多個人共用。在Python中,多態的例子有很多,最常見的恐怕就是len()這個內置函數了,你會發現不論是字符串,還是列表,或者元組等都可以使用len()來統計長度,類似這種接口復用的方法就體現了多態性。

多態性概念:允許你將父對象設置成為和一個或更多的他的子對象相等的技術,賦值之後,父對象就可以根據當前賦值給它的子對象的特性以不同的方式運作

class Animal:
    def __init__(self, name):
    ¦   self.name = name


class Dog(Animal):
    def eat(self):
    ¦   print('{0} eat cat'.format(self.name))


class Cat(Animal):
    def eat(self):
    ¦   print('{0} eat mouse'.format(self.name))


class Mouse(Animal):
    def eat(self):
    ¦   print('{0} eat {0}'.format(self.name))


def eat(obj):   # 一個接口,多種用法
    obj.eat()


dog = Dog('')
cat = Cat('')
mouse = Mouse('老鼠')

eat(dog)
eat(cat)
eat(mouse)
# 運行結果:
狗 eat cat
貓 eat mouse
老鼠 eat 老鼠

通過以上代碼應該可以很清晰的了解多態性

總結

Python面向對象的知識馬上就要結束了,還剩下特殊方法、舊類和新類的不同之處,下次見~

点赞

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *