鍍金池/ 教程/ Python/ 多態(tài)和封裝
標(biāo)準(zhǔn)庫 (4)
如何成為 Python 高手
標(biāo)準(zhǔn)庫 (6)
標(biāo)準(zhǔn)庫 (3)
類(2)
Pandas 使用 (2)
xml
用 tornado 做網(wǎng)站 (5)
文件(1)
練習(xí)
列表(3)
從小工到專家
除法
錯(cuò)誤和異常 (2)
函數(shù)(1)
用 tornado 做網(wǎng)站 (7)
為做網(wǎng)站而準(zhǔn)備
函數(shù)練習(xí)
標(biāo)準(zhǔn)庫 (8)
Pandas 使用 (1)
回顧 list 和 str
字典(1)
用 tornado 做網(wǎng)站 (3)
字符串(1)
函數(shù)(2)
寫一個(gè)簡單的程序
將數(shù)據(jù)存入文件
語句(5)
SQLite 數(shù)據(jù)庫
集成開發(fā)環(huán)境(IDE)
集合(1)
類(1)
用 tornado 做網(wǎng)站 (6)
用 tornado 做網(wǎng)站 (2)
自省
語句(4)
錯(cuò)誤和異常 (1)
用 tornado 做網(wǎng)站 (4)
集合(2)
列表(1)
標(biāo)準(zhǔn)庫 (1)
生成器
mysql 數(shù)據(jù)庫 (1)
第三方庫
實(shí)戰(zhàn)
運(yùn)算符
類(3)
字典(2)
語句(1)
數(shù)和四則運(yùn)算
語句(2)
文件(2)
MySQL 數(shù)據(jù)庫 (2)
電子表格
迭代器
mongodb 數(shù)據(jù)庫 (1)
特殊方法 (2)
特殊方法 (1)
字符編碼
編寫模塊
用 tornado 做網(wǎng)站 (1)
標(biāo)準(zhǔn)庫 (5)
函數(shù)(4)
類(5)
字符串(2)
關(guān)于 Python 的故事
函數(shù)(3)
字符串(4)
處理股票數(shù)據(jù)
常用數(shù)學(xué)函數(shù)和運(yùn)算優(yōu)先級(jí)
字符串(3)
為計(jì)算做準(zhǔn)備
多態(tài)和封裝
類(4)
迭代
語句(3)
錯(cuò)誤和異常 (3)
分析 Hello
Python 安裝
標(biāo)準(zhǔn)庫 (2)
列表(2)
元組

多態(tài)和封裝

前面講過的“繼承”,是類的一個(gè)重要特征,在編程中用途很多。這里要說兩個(gè)在理解和實(shí)踐上有爭議的話題:多態(tài)和封裝。所謂爭議,多來自于對同一個(gè)現(xiàn)象不同角度的理解,特別是有不少經(jīng)驗(yàn)豐富的程序員,還從其它語言的角度來詮釋 Python 的多態(tài)等。

多態(tài)

在網(wǎng)上搜索一下,發(fā)現(xiàn)對 Python 的多態(tài)問題,的確是仁者見仁智者見智。

作為一個(gè)初學(xué)者,不一定要也沒有必要、或者還沒有能力參與這種討論。但是,應(yīng)該理解 Python 中關(guān)于多態(tài)的基本體現(xiàn),也要對多態(tài)有一個(gè)基本的理解。

>>> "This is a book".count("s")
2
>>> [1,2,4,3,5,3].count(3)
2

上面的 count() 的作用是數(shù)一數(shù)某個(gè)元素在對象中出現(xiàn)的次數(shù)。從例子中可以看出,我們并沒有限定 count 的參數(shù)。類似的例子還有:

>>> f = lambda x,y:x+y

還記得這個(gè) lambda 函數(shù)嗎?如果忘記了,請復(fù)習(xí)函數(shù)(4)中對此的解釋。

>>> f(2,3)
5
>>> f("qiw","sir")
'qiwsir'
>>> f(["python","java"],["c++","lisp"])
['python', 'java', 'c++', 'lisp']

在那個(gè) lambda 函數(shù)中,我們沒有限制參數(shù)的類型,也一定不能限制,因?yàn)槿绻拗屏?,就不?Pythonic 了。在使用的時(shí)候,可以給參數(shù)任意類型,都能到的不報(bào)錯(cuò)的結(jié)果。當(dāng)然,這樣做之所以合法,更多的是來自于 + 的功能強(qiáng)悍。

以上,就體現(xiàn)了“多態(tài)”。當(dāng)然,也有人就此提出了反對意見,因?yàn)楸举|(zhì)上是在參數(shù)傳入值之前,Python 并沒有確定參數(shù)的類型,只能讓數(shù)據(jù)進(jìn)入函數(shù)之后再處理,能處理則罷,不能處理就報(bào)錯(cuò)。例如:

>>> f("qiw", 2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in <lambda>
TypeError: cannot concatenate 'str' and 'int' objects

本教程由于不屬于這種概念爭論范疇,所以不進(jìn)行這方面的深入探索,僅僅是告訴各位讀者相關(guān)信息。并且,本教程也是按照“人云亦云”的原則,既然大多數(shù)程序員都在討論多態(tài),那么我們就按照大多數(shù)人說的去介紹(盡管有時(shí)候真理掌握在少數(shù)人手中)。

“多態(tài)”,英文是:Polymorphism,在臺(tái)灣被稱作“多型”。維基百科中對此有詳細(xì)解釋說明。

多型(英語:Polymorphism),是指物件導(dǎo)向程式執(zhí)行時(shí),相同的訊息可能會(huì)送給多個(gè)不同的類別之物件,而系統(tǒng)可依劇物件所屬類別,引發(fā)對應(yīng)類別的方法,而有不同的行為。簡單來說,所謂多型意指相同的訊息給予不同的物件會(huì)引發(fā)不同的動(dòng)作稱之。

再簡化的說法就是“有多種形式”,就算不知道變量(參數(shù))所引用的對象類型,也一樣能進(jìn)行操作,來者不拒。比如上面顯示的例子。在 Python 中,更為 Pthonic 的做法是根本就不進(jìn)行類型檢驗(yàn)。

例如著名的 repr() 函數(shù),它能夠針對輸入的任何對象返回一個(gè)字符串。這就是多態(tài)的代表之一。

>>> repr([1,2,3])
'[1, 2, 3]'
>>> repr(1)
'1'
>>> repr({"lang":"python"})
"{'lang': 'Python'}"

使用它寫一個(gè)小函數(shù),還是作為多態(tài)代表的。

>>> def length(x):
...     print "The length of", repr(x), "is", len(x)
... 

>>> length("how are you")
The length of 'how are you' is 11
>>> length([1,2,3])
The length of [1, 2, 3] is 3
>>> length({"lang":"python","book":"itdiffer.com"})
The length of {'lang': 'python', 'book': 'itdiffer.com'} is 2

不過,多態(tài)也不是萬能的,如果這樣做:

>>> length(7)
The length of 7 is
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in length
TypeError: object of type 'int' has no len()

報(bào)錯(cuò)了??村e(cuò)誤提示,明確告訴了我們 object of type 'int' has no len()。

在諸多介紹多態(tài)的文章中,都會(huì)有這樣關(guān)于貓和狗的例子。這里也將代碼貼出來,讀者去體會(huì)所謂多態(tài)體現(xiàn)。其實(shí),如果你進(jìn)入了 Python 的語境,有時(shí)候是不經(jīng)意就已經(jīng)在應(yīng)用多態(tài)特性呢。

#!/usr/bin/env Python
# coding=utf-8

"the code is from: http://zetcode.com/lang/python/oop/"

__metaclass__ = type

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

    def talk(self):
        pass

class Cat(Animal):
    def talk(self):
        print "Meow!"

class Dog(Animal):
    def talk(self):
        print "Woof!"

a = Animal()
a.talk()

c = Cat("Missy")
c.talk()

d = Dog("Rocky")
d.talk()

保存后運(yùn)行之:

$ python 21101.py 
Meow!
Woof!

代碼中有 Cat 和 Dog 兩個(gè)類,都繼承了類 Animal,它們都有 talk() 方法,輸入不同的動(dòng)物名稱,會(huì)得出相應(yīng)的結(jié)果。

關(guān)于多態(tài),有一個(gè)被稱作“鴨子類型”(duck typeing)的東西,其含義在維基百科中被表述為:

在程序設(shè)計(jì)中,鴨子類型(英語:duck typing)是動(dòng)態(tài)類型的一種風(fēng)格。在這種風(fēng)格中,一個(gè)對象有效的語義,不是由繼承自特定的類或?qū)崿F(xiàn)特定的接口,而是由當(dāng)前方法和屬性的集合決定。這個(gè)概念的名字來源于由 James Whitcomb Riley 提出的鴨子測試(見下面的“歷史”章節(jié)),“鴨子測試”可以這樣表述:“當(dāng)看到一只鳥走起來像鴨子、游泳起來像鴨子、叫起來也像鴨子,那么這只鳥就可以被稱為鴨子。”

對于鴨子類型,也是有爭議的。這方面的詳細(xì)信息,讀者可以去看有關(guān)維基百科的介紹。

對于多態(tài)問題,最后還要告誡讀者,類型檢查是毀掉多態(tài)的利器,比如 type、isinstance 以及 isubclass 函數(shù),所以,一定要慎用這些類型檢查函數(shù)。

封裝和私有化

在正式介紹封裝之前,先扯個(gè)笑話。

某軟件公司老板,號(hào)稱自己懂技術(shù)。一次有一個(gè)項(xiàng)目要交付給客戶,但是他有不想讓客戶知道實(shí)現(xiàn)某些功能的代碼,但是交付的時(shí)候要給人家代碼的。于是該老板就告訴程序員,“你們把那部分核心代碼封裝一下”。程序員聽了之后,迷茫了。

不知道你有沒有笑。

“封裝”,是不是把代碼寫到某個(gè)東西里面,“人”在編輯器中打開,就看不到了呢?除非是你的顯示器壞了。

在程序設(shè)計(jì)中,封裝(Encapsulation)是對 object 的一種抽象,即將某些部分隱藏起來,在程序外部看不到,即無法調(diào)用(不是人用眼睛看不到那個(gè)代碼,除非用某種加密或者混淆方法,造成現(xiàn)實(shí)上的困難,但這不是封裝)。

要了解封裝,離不開“私有化”,就是將類或者函數(shù)中的某些屬性限制在某個(gè)區(qū)域之內(nèi),外部無法調(diào)用。

Python 中私有化的方法也比較簡單,就是在準(zhǔn)備私有化的屬性(包括方法、數(shù)據(jù))名字前面加雙下劃線。例如:

#!/usr/bin/env Python
# coding=utf-8

__metaclass__ = type

class ProtectMe:
    def __init__(self):
        self.me = "qiwsir"
        self.__name = "kivi"

    def __python(self):
        print "I love Python."

    def code(self):
        print "Which language do you like?"
        self.__python()

if __name__ == "__main__":
    p = ProtectMe()
    print p.me
    print p.__name

運(yùn)行一下,看看效果:

$ python 21102.py
qiwsir
Traceback (most recent call last):
  File "21102.py", line 21, in <module>
    print p.__name
AttributeError: 'ProtectMe' object has no attribute '__name'

查看報(bào)錯(cuò)信息,告訴我們沒有__name 那個(gè)屬性。果然隱藏了,在類的外面無法調(diào)用。再試試那個(gè)函數(shù),可否?

if __name__ == "__main__":
    p = ProtectMe()
    p.code()
    p.__python()

修改這部分即可。其中 p.code() 的意圖是要打印出兩句話:"Which language do you like?""I love Python.",code() 方法和__python() 方法在同一個(gè)類中,可以調(diào)用之。后面的那個(gè) p.__Python() 試圖調(diào)用那個(gè)私有方法。看看效果:

$ python 21102.py 
Which language do you like?
I love Python.
Traceback (most recent call last):
  File "21102.py", line 23, in <module>
    p.__python()
AttributeError: 'ProtectMe' object has no attribute '__python'

如愿以償。該調(diào)用的調(diào)用了,該隱藏的隱藏了。

用上面的方法,的確做到了封裝。但是,我如果要調(diào)用那些私有屬性,怎么辦?

可以使用 property 函數(shù)。

#!/usr/bin/env Python
# coding=utf-8

__metaclass__ = type

class ProtectMe:
    def __init__(self):
        self.me = "qiwsir"
        self.__name = "kivi"

    @property
    def name(self):
        return self.__name

if __name__ == "__main__":
    p = ProtectMe()
    print p.name

運(yùn)行結(jié)果:

$ python 21102.py 
kivi

從上面可以看出,用了 @property 之后,在調(diào)用那個(gè)方法的時(shí)候,用的是 p.name 的形式,就好像在調(diào)用一個(gè)屬性一樣,跟前面 p.me 的格式相同。

看來,封裝的確不是讓“人看不見”。


總目錄   |   上節(jié):類(5)   |   下節(jié):更多屬性(1)

如果你認(rèn)為有必要打賞我,請通過支付寶:qiwsir@126.com,不勝感激。

上一篇:元組下一篇:mysql 數(shù)據(jù)庫 (1)