歡迎光臨
每天分享高質量文章

深入理解類與物件

(點選上方公眾號,可快速關註一起學Python)

作者:浪子燕青       連結:

http://www.langzi.fun/深入類與物件-上.html

 

這次開始深入瞭解類與物件,再此之前,先把以前的知識點溫習一遍吧。Python面向物件程式設計

多型與鴨子函式

類的三大特徵,封裝繼承與多型,前面兩個都好理解,關於多型則是本次要重點研究的物件。

多型:根據物件型別的不同以不同的方式進行處理。

定義總是抽象枯燥的,上程式碼演示演示

class Animal(object):
    def gaga():
        print('gagagagagagagaga')

class dog(Animal):
    pass
dog.gaga()

傳回結果:

gagagagagagagaga

透過實體來分析研究執行原理:

  1. 首先定義一個類Animal,具有一個gaga方法
  2. 然後定義一個類dog,繼承Animal
  3. 執行dog.gaga()
  4. 這個時候會呼叫父類Animal的gaga方法

ok,這個就是類的特性之繼承,學會了的老鐵刷個飛機鼓勵鼓勵~~

封裝是啥,就是你的類方法都統一定義好提供一個API進行呼叫,雙擊666啊~~

重點是多型,這是一個極其非常十分相當抽象但是不是很抽象的概念,概念雖然有些難理解,但是程式碼上一遍就能理解了。

class dog:
    def gaga():
        print('汪汪汪汪汪~')
class cat:
    def gaga():
        print('喵喵喵喵喵喵喵~')
def you_say(you):
    you.gaga()
    you.gaga()
you_say(dog)
print('-'*10)
you_say(cat)

傳回結果:

汪汪汪汪汪~
汪汪汪汪汪~
----------
喵喵喵喵喵喵喵~
喵喵喵喵喵喵喵~

要不要問下神奇的海螺這是什麼情況?這就是多型,是python如此簡介性的重要基礎。嘗試來分析一下:

  1. 定義兩個類cat與dog,他們都有同一個方法,會發出嬌聲(叫聲)
  2. 然後定義一個函式,引數為you,作用是呼叫you.gaga(),還呼叫兩次
  3. 然後執行,傳入的是dog類,此時的you繼承的是dog類,那麼就能直接使用dog類的gaga()方法
  4. 同理喵喵喵也是這樣的。

是不是有些似成相似的感覺?給你看看下麵的程式碼你就會恍然大悟了。

s = 'langzi'
b  = [1,2,3,4,5,6,7,8,9]
print(len(s))
print(len(b))

結果就不輸出了。同樣是len函式,但是傳入的確實兩種完完全全不同型別的數值,卻能正常執行….神奇嗎?

再次回到多型的定義:根據物件型別的不同以不同的方式進行處理。

是不是有點感覺了?沒錯,你已經窺視到了python最基礎核心的東西,多型,對傳入的不同型別按照不同方式處理,從而進一步的簡化面向物件程式設計。就像說到了函式的作用域就會引申出閉包,說到了多型的概念就會引出python核心之鴨子函式。

鴨子函式:如果它像鴨子一樣走路,像鴨子一樣叫,那麼它就是一隻鴨子。

鴨子函式也有幾個高大上的名字,比較高階的方式是叫做「隱式型別」或者「結構式型別」。

在舉個例子來回顧一下剛剛學習到的知識:

d = [0]
s = [1,2,3,4,5]
b = (6,7,8,9)
d.extend(s)
d.extend(b)
print(d)

傳回結果:

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

extend函式什麼意思,以前一直以為就是合併串列,但是沒想到吧,能把串列和元組都能合併呢~按ctrl+alt+b檢視extend函式的原始碼:

def extend(self,iterable):
    pass

可以看到該函式接受的是一個可迭代物件,那能不能合併一個字串呢?答案是可以的。

嘗試理解和想象一下extend與串列,字串,元組的關係。是不是就像最上面的喵喵喵和汪汪汪?

字串,元組,串列都是可迭代物件,他們的構成類中都有iter這個魔法函式,就好像dog類和cat類都有gaga函式一樣,然後extend會呼叫這個iter魔法函式,就好像you_say呼叫繼承的類的gaga函式一樣?

這是不是說,只要一個類中具有iter或者getitem這個魔法函式,這個類具有可迭代功能,就能使用extend函式呢?看看程式碼:

class magic:
    def __init__(self,num):
        self.num = num
    def __getitem__(self, item):
        return self.num[item]

a = magic([1,2,3,4,5])
b = [666]
b.extend(a)
# 想一想為啥不能用a.extend(b)
print(b)

傳回結果:

[666, 1, 2, 3, 4, 5]

鵝妹子嚶,如果你突然有一種奇妙的感覺,恭喜你,你已經不小心窺探到了python的密碼了。Python 不檢查傳入的物件的型別,使用起來更加靈活。

有如下的一段程式碼:

class A(object):
    def show(self):
        print 'base show'

class B(A):
    def show(self):
        print 'derived show'

obj = B()
obj.show()

如何呼叫類A的show方法了。
方法如下:

obj.__class__ = A
obj.show()

__class__方法指向了類物件,只用給他賦值型別A,然後呼叫方法show,但是用完了記得修改回來。

抽象基類

預設情況下,Python解析器不強制檢查對抽象類的繼承,即抽象類的子類可能沒有實現其中的抽象方法,但是Python並不會報錯。

為了避免這種情況,從Python 3.4/2.6開始,Python標準庫中提供了abc模組(Abstract Base Classes),為定義Python的抽象基類提供了公共基礎。

參考來源:

https://blog.csdn.net/taiyangdao/article/details/78199623

抽象基類的使用:

1. 直接繼承:直接繼承抽象基類的子類就沒有這麼靈活,抽象基類中可以宣告”抽象方法“和“抽象屬性”,只有完全覆寫(實現)了抽象基類中的“抽象”內容後,才能被實體化,而虛擬子類則不受此影響。
2. 虛擬子類: 將其他的類”註冊“到抽象基類下當虛擬子類(呼叫register方法),虛擬子類的好處是你實現的第三方子類不需要直接繼承自基類,可以實現抽象基類中的部分API介面,也可以根本不實現,但是issubclass(), issubinstance()進行判斷時仍然傳回真值。

抽象基類有兩個特點:

1.規定繼承類必須具有抽象基類指定的方法(強制子類具有某個方法)

2.抽象基類無法實體化

基於上述兩個特點,抽象基類主要用於介面設計

實現抽象基類可以使用內建的abc模組(所有的抽象基類繼承的maeacalss=ABCMeta,這個暫時我也不會,等學到元類程式設計的時候據說就會了。)

在python中有hasattr函式,用來檢查一個類是否具有某個介面功能,舉個例子:

import requests
print(hasattr(requests,'get'))

傳回結果:

True

關於抽象基類的實際運用,用程式碼來解釋一番。

class magic(object):
    def run(self):
        return self + ':Run'

    def stop(self):
        return self + ':Stop'

a = magic
print(a.run('a'))
print(a.stop('a'))
print('-'*10)
class magics(magic):
    def run(self,name):
        return name + ':runnging'

b = magics()
print(b.run('浪子'))

傳回結果:

a:Run
a:Stop
----------
浪子:runnging

很顯然,這個想讓你明白這個就是類方法的重寫,然而重點並不在這裡。

這一章節講的是抽象基類,他的作用是:你寫的類A,B。其中A是一個非常非常核心的類,具有一些非常非常牛逼的功能,B不過是一個小垃圾,做一些簡單的事情,但是在實際情況中你呼叫的是B類,其中B繼承A。

但是吧,你希望你在寫B類的時候,要擁有A類的全部方法,這個就是抽象基類的概念。(A是B的基類)

但是在前面的鴨子型別中說到過,python類傳遞引數的時候,是不會檢查他的型別的,只有在執行的時候發生了報錯你才知道問題所在。

那麼如何實現強制讓自己寫的類B在繼承A的自己想要並且必須要有的功能(有人會問,這有啥用?其實不然,這個我個人認為涉及到語言層面設計問題,在某些相同功能的程式碼領域,為了保持相同程式碼功能的統一性,或者說提供相同的API),這個就是抽象基類的作用了。

繼續回到例子,我希望我寫的magics類必須要有magic類中的run和stop方法,那該怎麼弄呢?

import abc
class magic(object,metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def run(self):
        return self + ':Run'

    @abc.abstractmethod
    def stop(self):
        return self + ':Stop'

a = magic
print(a.run('a'))
print(a.stop('a'))
print('-'*10)

class magics(magic):
    def run(self,name):
        return name + ':runnging'
    def stop(self):
        pass

b = magics()
print(b.run('浪子'))

這樣就好辣,不要問我abc.ABCMeta是什麼意思,這些會在後面的元類程式設計中慢慢學,是要是被@abc.abstractmethod裝飾器裝飾的函式,都需要保證你自己寫的類中有這個函式功能哦,不然就會要你好看。

最後在梳理一下:抽象基類可以讓你做介面的強制規定。

isinstance與type區別

isinstance的功能在前面的文章提到過,就是判斷傳入物件的型別,相關的還有hasattr,issubclass,但是這裡主要講的是isinstance的原理。

看看程式碼:

class A:
    pass

class B(A):
    pass

print(issubclass(B,A))
print(isinstance(B,A))

傳回結果:

True
False

這個很好理解對吧,再繼續往下走

class A:
    pass

class B(A):
    pass

print(issubclass(B,A))
print(isinstance(B,A))
print('-'*10)
c = A()
d = B()
print(isinstance(c,A))
print(isinstance(c,B))
print(isinstance(d,A))
print(isinstance(d,B))

傳回結果:

True
False
----------
True
False
True
True

這個其實也很好理解對吧。

繼續走:

class A:
    pass

class B(A):
    pass

d = B()
print(type(B))
# 列印類B的型別:
print(type(d))
# 列印實體化物件d的型別:
print(B)
# 列印類B:
print(d)
# 列印實體化物件d:<__main__.b object="" at=""/>
print(type(d) is B)
# 判斷實體化物件d的型別是不是就是B:True

傳回結果:




<__main__.b object="" at="">
True</__main__.b>

那麼type(d)是否is A呢?答案是False,因為這個時候d已經指向了類B,雖然B繼承了A,但是他們是不一樣的。

但是使用isinstance的時候,傳回的確實True,因為isinstance會根據類的繼承關係找到最初的基類,從而判斷是否屬於一個型別的。

這裡穿插一下is 與 == 的區別,在這一開頭就介紹了類的三大特性,型別(type),值(value),身份(id)

is:只要id相同,就傳回True
==:只要value相同,就傳回True

比如 1 == 1.0就傳回True,1 is 1.0 就傳回False。

梳理:type只能稍微判斷檢查一些你這個物件的型別,不能進一步的跟蹤及繼承關係,isinstance卻可以進一步的跟蹤繼承關係。

類變數與物件變數

這個很好理解吧,有過面向物件基礎的同學一看就知道是啥,物件變數也叫實體的變數。

這裡很有必要回顧一下變數的作用域與面對物件程式設計的一些基礎知識。

作用域:在作用域的內部可以訪問這個變數,但是在外部沒辦法訪問這個作用域裡面的變數。

Python中,函式的作用域是最低階的作用域,函式內部的變數只能在函式內部起作用。

python的四層作用域

  1. 區域性作用域
  2. 閉包函式外的函式中
  3. 全域性作用域
  4. 內建作用域

來上程式碼:

class magic:
    a = 'langzi'
    def __init__(self,x):
        self.x = x
        # 這裡傳入的x已經屬於這個物件

    def run(self):
        return self.x

m = magic('浪子')
print(m.a)
print(m.run())

傳回結果:

langzi
浪子

這裡的a屬於類的變數,透過呼叫m.a或者magic.a都可以呼叫,self.x是實體的變數,這裡影藏了一個小坑,看程式碼:

class magic:
    a = 'langzi'
    def __init__(self,x):
        self.x = x
        # 這裡傳入的x已經屬於這個物件

m = magic('xx')
m.a = 'LANGZILANGZI'
print(m.a)
print(magic.a)
print('-'*10)
magic.a = '浪子浪子'
print(m.a)
print(magic.a)

傳回結果:

LANGZILANGZI
langzi
----------
LANGZILANGZI
浪子浪子

透過m.a和magic.a兩種方法對類變數進行修改,都是檢視類變數,但是結果卻不一樣,看看這裡面做了什麼。

  1. 當magic類實體成m物件的時候,這個時候magic.a是保持不變的
  2. 當使用m.a呼叫賦值的時候,會新建一個m.a屬性(是新建一個哦),放在m實體的一個屬性值當中
  3. 所以說m.a和magic.a是獨立開來的
  4. 使用mgic.a修改的是magic類的屬性
  5. 當使用m.a時候,會優先從物件m中查詢是否有m.a這個新的變數
  6. 但是magic類的變數還是之前原來的
  7. 類變數與實體的變數,是兩個不同的屬性

    贊(0)

    分享創造快樂