Python從設(shè)計之初就是一門面向?qū)ο笳Z言,它提供一些語言特性支持面向?qū)ο缶幊獭?br> 創(chuàng)建對象是Python的核心概念娱俺,本章將介紹如何創(chuàng)建對象际看,以及多態(tài)、封裝矢否、方法和繼承等概念仲闽。
理解面向?qū)ο?/h2>
什么是面向?qū)ο缶幊?/h3>
Python是一門面向?qū)ο缶幊陶Z言,對面向?qū)ο笳Z言編碼的過程叫作面向?qū)ο缶幊獭?br>
面向?qū)ο缶幊蹋∣bject Oriented Programming, OOP)是一種程序設(shè)計思想僵朗。OOP把對象作為程序的基本單元赖欣,一個對象包含數(shù)據(jù)和操作數(shù)據(jù)的函數(shù)屑彻。
面向?qū)ο蟪绦蛟O(shè)計把計算機程序視為一組對象的集合,每個對象都可以接收其他對象發(fā)過來的消息顶吮,并處理這些消息社牲,計算機程序的執(zhí)行就是一系列消息在各個對象之間傳遞。
在Python中悴了,所有數(shù)據(jù)類型都被視為對象搏恤,也可以自定義對象。自定義對象數(shù)據(jù)類型就是面向?qū)ο笾械念悾–lass)的概念湃交。
面向?qū)ο笮g(shù)語簡介
在開始具體介紹面向?qū)ο蠹夹g(shù)之前熟空,我們先了解一些面向?qū)ο蟮男g(shù)語,以便在后續(xù)內(nèi)容中碰到對應(yīng)詞時能明白這些術(shù)語的意思搞莺。
- 類:用來描述具有相同屬性和方法的對象的集合息罗。類定義了集合中每個對象共有的屬性和方法。對象是類的實例才沧。
- 類變量(屬性):類變量在整個實例化的對象中是公用的迈喉。類變量定義在類中,且在方法之外温圆。類變量通常不作為實例變量使用挨摸。類變量也稱作屬性。
- 數(shù)據(jù)成員:類變量或?qū)嵗兞坑糜谔幚眍惣捌鋵嵗龑ο蟮南嚓P(guān)數(shù)據(jù)岁歉。
- 方法重寫:如果從父類繼承的方法不能滿足子類的需求油坝,就可以對其進行改寫,這個過程稱為方法的覆蓋(Override)刨裆,也稱為方法的重寫澈圈。
- 實例變量:定義在方法中的變量只作用于當前實例的類。
- 多態(tài)(Polymorphism):對不同類的對象使用同樣的操作帆啃。
- 封裝(Encapsulation):對外部世界隱藏對象的工作細節(jié)瞬女。
- 繼承(Inheritance):即一個派生類(derived class)繼承基類(base class)的字段和方法。繼承允許把一個派生類的對象作為一個基類對象對待努潘,以普通類為基礎(chǔ)建立專門的類對象诽偷。
- 實例化(Instance):創(chuàng)建一個類的實例、類的具體對象疯坤。
- 方法:類中定義的函數(shù)报慕。
- 對象:通過類定義的數(shù)據(jù)結(jié)構(gòu)實例。對象包括兩個數(shù)據(jù)成員(類變量和實例變量)和方法压怠。
和其他編程語言相比眠冈,Python在盡可能不增加新語法和語義的情況下加入了類機制。
Python中的類提供了面向?qū)ο缶幊痰乃谢竟δ埽侯惖睦^承機制允許多個基類菌瘫、派生類可以覆蓋基類中的任何方法蜗顽、方法中可以調(diào)用基類中的同名方法布卡。
對象可以包含任意數(shù)量和類型的數(shù)據(jù)。
類的定義與使用
1 類的定義
開始介紹前先看一個類的示例:
class MyClass(object):
i = 123
def f(self):
return 'hello world'
由上面的代碼可以得知雇盖,類定義的語法格式如下:
class ClassName(object):
<statement-1>
.
.
.
<statement-N>
由代碼片段和類定義我們看到忿等,Python中定義類使用class關(guān)鍵字,class后面緊接著類名崔挖,如示例中的MyClass贸街,類名通常是大寫開頭的單詞;緊接著是(object)狸相,表示該類是從哪個類繼承下來的薛匪。通常,如果沒有合適的繼承類卷哩,就使用object類蛋辈,這是所有類最終都會繼承的類属拾。類包含屬性(相當于函數(shù)中的語句)和方法(類中的方法大體可以理解成函數(shù))将谊。
提示:在類中定義方法的形式和函數(shù)差不多,但不稱為函數(shù)渐白,而稱為方法尊浓。方法的調(diào)用需要綁定到特定對象上,而函數(shù)不需要纯衍。我們后面會逐步接觸方法的調(diào)用方式栋齿。
2 類的使用
本節(jié)簡單講述類的使用。以8.2.1小節(jié)的示例為例(別忘了寫開頭兩行)襟诸,保存并執(zhí)行(程序編寫完成后瓦堵,需要將文件保存為后綴為.py的文件,在cmd命令窗口下執(zhí)行.py文件):
#! /usr/bin/python3
# -*-coding:UTF-8-*-
class MyClass(object):
i = 123
def f(self):
return 'hello world'
use_class = MyClass()
print('調(diào)用類的屬性:',use_class.i)
print('調(diào)用類的方法:',use_class.f())
執(zhí)行結(jié)果如下:
調(diào)用類的屬性: 123
調(diào)用類的方法: hello world
由輸入代碼中的調(diào)用方式可知歌亲,類的使用比函數(shù)調(diào)用多了幾個操作菇用,調(diào)用類時需要執(zhí)行如下操作:
use_class = MyClass()
這步叫作類的實例化,即創(chuàng)建一個類的實例陷揪。此處得到的use_class變量稱為類的具體對象惋鸥。再看后面兩行的調(diào)用:
print('調(diào)用類的屬性:',use_class.i)
print('調(diào)用類的方法:',use_class.f())
這里第一行后的use_class.i用于調(diào)用類的屬性,也就是我們前面所說的類變量悍缠。第二行后的use_class.f()用于調(diào)用類的方法卦绣。
在上面的示例中,在類中定義f()方法時帶了一個self參數(shù)飞蚓,該參數(shù)在方法中并沒有被調(diào)用滤港,是否可以不要呢?調(diào)用f()方法時沒有傳遞參數(shù)趴拧,是否表示參數(shù)可以傳遞也可以不傳遞蜗搔?
對于在類中定義方法的要求:在類中定義方法時劲藐,第一個參數(shù)必須是self。除第一個參數(shù)外樟凄,類的方法和普通函數(shù)沒什么區(qū)別聘芜,如可以用默認參數(shù)、可變參數(shù)缝龄、關(guān)鍵字參數(shù)和命名關(guān)鍵字參數(shù)等汰现。
對于在類中調(diào)用方法的要求:要調(diào)用一個方法,在實例變量上直接調(diào)用即可叔壤。除了self不用傳遞瞎饲,其他參數(shù)正常傳入。
類對象支持兩種操作炼绘,即屬性引用和實例化嗅战。屬性引用的標準語法如下:
obj.name
語法中obj代表類對象,name代表屬性俺亮。
深入類
將深入介紹類的相關(guān)內(nèi)容驮捍,如類的構(gòu)造方法和訪問權(quán)限。
1 類的構(gòu)造方法
在開始介紹前脚曾,我們對前面的示例做一些改動东且,代碼如下:
#! /usr/bin/python3
# -*-coding:UTF-8-*-
class MyClass(object):
i = 123
def __init__(self, name):
self.name = name
def f(self):
return 'hello,'+ self.name
use_class = MyClass('xiaoming')
print('調(diào)用類的屬性:',use_class.i)
print('調(diào)用類的方法:',use_class.f())
程序執(zhí)行結(jié)果如下:
調(diào)用類的屬性: 123
調(diào)用類的方法: hello,xiaoming
若類的實例化語句寫法和之前一樣,即:
use_class = MyClass()
程序執(zhí)行結(jié)果如下:
Traceback (most recent call last):
File "D:/python/workspace/classdef.py", line 21, in <module>
use_class = MyClass()
TypeError: __init__() missing 1 required positional argument: 'name'
從代碼和輸出結(jié)果看到本讥,實例化MyClass類時調(diào)用了__init__()
方法珊泳。這里就奇怪了,我們在代碼中并沒有指定調(diào)用__init__()
方法拷沸,怎么會報__init__()
方法錯誤呢色查?
在Python中,__init__()
方法是一個特殊方法撞芍,在對象實例化時會被調(diào)用秧了。__init__()
的意思是初始化,是initialization的簡寫勤庐。這個方法的書寫方式是:先輸入兩個下劃線示惊,后面接著init,再接著兩個下劃線愉镰。這個方法也叫構(gòu)造方法米罚。在定義類時,若不顯式地定義一個__init__()
方法丈探,則程序默認調(diào)用一個無參的__init__()
方法录择。比如以下兩段代碼的使用效果是一樣的:
代碼一:
#! /usr/bin/python3
# -*-coding:UTF-8-*-
class DefaultInit(object):
def __init__(self):
print('類實例化時執(zhí)行我,我是__init__方法。')
def show(self):
print ('我是類中定義的方法隘竭,需要通過實例化對象調(diào)用塘秦。')
test = DefaultInit()
print('類實例化結(jié)束。')
test.show()
程序執(zhí)行結(jié)果如下:
類實例化時執(zhí)行我动看,我是
__init__
方法尊剔。
類實例化結(jié)束。
我是類中定義的方法菱皆,需要通過實例化對象調(diào)用须误。
代碼二:
#! /usr/bin/python3
# -*-coding:UTF-8-*-
class DefaultInit(object):
def show(self):
print ('我是類中定義的方法,需要通過實例化對象調(diào)用仇轻。')
test = DefaultInit()
print('類實例化結(jié)束京痢。')
test.show()
程序執(zhí)行結(jié)果如下:
類實例化結(jié)束。
我是類中定義的方法篷店,需要通過實例化對象調(diào)用祭椰。
由上面兩段代碼的輸出結(jié)果看到,當代碼中定義了__init__()
方法時疲陕,實例化類時會調(diào)用該方法方淤;若沒有定義__init__()
方法,實例化類時也不會報錯鸭轮,此時調(diào)用默認的__init__()
方法臣淤。
在Python中定義類時若沒有定義構(gòu)造方法(__init__()
方法)橄霉,則在類的實例化時系統(tǒng)調(diào)用默認的構(gòu)造方法窃爷。另外恬汁,__init__()
方法可以有參數(shù)澎埠,參數(shù)通過init()傳遞到類的實例化操作上新娜。
既然__init__()
方法是Python中的構(gòu)造方法律秃,那么是否可以在一個類中定義多個構(gòu)造方法呢抗楔?我們先看如下3段代碼:
代碼一:
#! /usr/bin/python3
# -*-coding:UTF-8-*-
class DefaultInit(object):
def __init__(self):
print('我是不帶參數(shù)的__init__方法柳弄。')
DefaultInit()
print('類實例化結(jié)束弧关。')
程序執(zhí)行結(jié)果如下:
我是不帶參數(shù)的
__init__
方法嘱根。
類實例化結(jié)束束莫。
在只有一個__init__()
方法時懒棉,實例化類沒有什么顧慮。
代碼二:
#! /usr/bin/python3
# -*-coding:UTF-8-*-
class DefaultInit(object):
def __init__(self):
print('我是不帶參數(shù)的__init__方法览绿。')
def __init__(self, param):
print('我是帶一個參數(shù)的__init__方法策严,參數(shù)值為:',param)
DefaultInit('hello')
print('類實例化結(jié)束。')
程序執(zhí)行結(jié)果如下:
我是帶一個參數(shù)的
__init__
方法饿敲,參數(shù)值為: hello
類實例化結(jié)束妻导。
由執(zhí)行結(jié)果看到,調(diào)用的是帶了一個param參數(shù)的構(gòu)造方法,若把類的實例化語句更改為:
DefaultInit()
執(zhí)行結(jié)果為:
Traceback (most recent call last):
File "D:/python/workspace/classdef.py", line 59, in <module>
DefaultInit()
TypeError: __init__() missing 1 required positional argument: 'param'
或更改為:
DefaultInit('hello', 'world')
執(zhí)行結(jié)果為:
Traceback (most recent call last):
File "D:/python/workspace/classdef.py", line 61, in <module>
DefaultInit('hello', 'world')
TypeError: __init__() takes 2 positional arguments but 3 were given
由執(zhí)行結(jié)果看到倔韭,實例化類時只能調(diào)用帶兩個占位參數(shù)的構(gòu)造方法术浪,調(diào)用其他構(gòu)造方法都會報錯。
代碼三:
#! /usr/bin/python3
# -*-coding:UTF-8-*-
class DefaultInit(object):
def __init__(self, param):
print('我是帶一個參數(shù)的__init__方法寿酌,參數(shù)值為:',param)
def __init__(self):
print('我是不帶參數(shù)的__init__方法胰苏。')
DefaultInit()
print('類實例化結(jié)束。')
程序執(zhí)行結(jié)果如下:
我是不帶參數(shù)的
__init__
方法醇疼。
類實例化結(jié)束碟联。
由執(zhí)行結(jié)果看到,調(diào)用的構(gòu)造方法除了self外僵腺,沒有其他參數(shù)鲤孵。若把類的實例化語句更改為如下:
DefaultInit('hello')
執(zhí)行結(jié)果為:
Traceback (most recent call last):
File "D:/python/workspace/classdef.py", line 60, in <module>
DefaultInit('hello')
TypeError: __init__() takes 1 positional argument but 2 were given
或更改為:
DefaultInit('hello', 'world')
執(zhí)行結(jié)果為:
Traceback (most recent call last):
File "D:/python/workspace/classdef.py", line 61, in <module>
DefaultInit('hello', 'world')
TypeError: __init__() takes 2 positional arguments but 3 were given
由執(zhí)行結(jié)果看到,實例化類時只能調(diào)用帶一個占位參數(shù)的構(gòu)造方法辰如,調(diào)用其他構(gòu)造方法都會報錯普监。
由以上幾個示例我們得知:一個類中可定義多個構(gòu)造方法,但實例化類時只實例化最后的構(gòu)造方法琉兜,即后面的構(gòu)造方法會覆蓋前面的構(gòu)造方法凯正,并且需要根據(jù)最后一個構(gòu)造方法的形式進行實例化。建議一個類中只定義一個構(gòu)造函數(shù)豌蟋。
2 類的訪問權(quán)限
在類內(nèi)部有屬性和方法廊散,外部代碼可以通過直接調(diào)用實例變量的方法操作數(shù)據(jù),這樣就隱藏了內(nèi)部的復(fù)雜邏輯梧疲,例如:
#! /usr/bin/python3
# -*-coding:UTF-8-*-
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
def info(self):
print('學生:%s允睹;分數(shù): %s' % (self.name, self.score))
stu = Student('xiaomeng',95)
print ('修改前分數(shù):', stu.score)
stu.info()
stu.score=0
print ('修改后分數(shù):', stu.score)
stu.info()
程序執(zhí)行結(jié)果如下:
修改前分數(shù):95
學生:xiaomeng;分數(shù): 95
修改后分數(shù):0
學生:xiaomeng幌氮;分數(shù): 0
由代碼和輸出結(jié)果看到缭受,在類中定義的非構(gòu)造方法可以調(diào)用類中構(gòu)造方法實例變量的屬性,調(diào)用的方式為self.實例變量屬性名该互,如代碼中的self.name和self.score米者。可以在類的外部修改類的內(nèi)部屬性宇智。如果要讓內(nèi)部屬性不被外部訪問蔓搞,該怎么辦呢?
要讓內(nèi)部屬性不被外部訪問随橘,可以在屬性名稱前加兩個下劃線__
喂分。在Python中,實例的變量名如果以__
開頭太防,就會變成私有變量(private)妻顶,只有內(nèi)部可以訪問酸员,外部不能訪問。據(jù)此讳嘱,我們把Student類改一改:
#! /usr/bin/python3
# -*-coding:UTF-8-*-
class Student(object):
def __init__(self, name, score):
self.__name = name
self.__score = score
def info(self):
print('學生:%s幔嗦;分數(shù): %s' % (self.__name, self.__score))
stu = Student('xiaomeng',95)
print('修改前分數(shù):', stu.__score)
stu.info()
stu.__score = 0
print('修改后分數(shù):',stu.__score)
stu.info()
程序執(zhí)行結(jié)果如下:
Traceback (most recent call last):
File "D:/python/workspace/classdef.py", line 81, in <module>
print('修改前分數(shù):', stu.__score)
AttributeError: 'Student' object has no attribute '__score'
由執(zhí)行結(jié)果看到,我們已經(jīng)無法從外部訪問實例變量的屬性__score
了沥潭。這樣有什么作用呢邀泉?
這樣可以確保外部代碼不能隨意修改對象內(nèi)部的狀態(tài),通過訪問限制的保護钝鸽,代碼更加安全汇恤。比如上面的分數(shù)對象是一個比較重要的內(nèi)部對象,如果外部可以隨便更改這個值拔恰,大家都隨便更改自己成績表單中的分數(shù)因谎,豈不是很混亂。
如果外部代碼要獲取類中的name和score怎么辦呢颜懊?
在Python中财岔,可以為類增加get_attrs
方法,獲取類中的私有變量河爹,例如在上面的示例中添加get_score
(name的使用方式類同)方法匠璧,代碼如下:
#! /usr/bin/python3
# -*-coding:UTF-8-*-
class Student(object):
def __init__(self, name, score):
self.__name = name
self.__score = score
def info(self):
print('學生:%s;分數(shù): %s' % (self.__name, self.__score))
def get_score(self):
return self.__score
stu = Student('xiaomeng',95)
print('修改前分數(shù):', stu.get_score())
stu.info()
print('修改后分數(shù):',stu.get_score())
stu.info()
執(zhí)行結(jié)果如下:
修改前分數(shù): 95
學生:xiaomeng咸这;分數(shù): 95
修改后分數(shù): 95
學生:xiaomeng夷恍;分數(shù): 95
由執(zhí)行結(jié)果看到,通過get_score方法已經(jīng)可以正確得到類內(nèi)部的屬性值媳维。
是否可以通過外部更改內(nèi)部私有變量的值呢酿雪?
在Python中,可以為類增加set_attrs方法侨艾,修改類中的私有變量执虹,如更改上面示例中的score屬性值拓挥,可以添加set_score
(name使用方式類同)方法唠梨,代碼如下:
#! /usr/bin/python3
# -*-coding:UTF-8-*-
class Student(object):
def __init__(self, name, score):
self.__name = name
self.__score = score
def info(self):
print('學生:%s;分數(shù): %s' % (self.__name, self.__score))
def get_score(self):
return self.__score
def set_score(self, score):
self.__score = score
stu = Student('xiaomeng',95)
print('修改前分數(shù):', stu.get_score())
stu.info()
stu.set_score(0)
print('修改后分數(shù):',stu.get_score())
stu.info()
程序執(zhí)行結(jié)果如下:
修改前分數(shù): 95
學生:xiaomeng侥啤;分數(shù): 95
修改后分數(shù): 0
學生:xiaomeng当叭;分數(shù): 0
由程序執(zhí)行結(jié)果看到,通過set_score
方法正確更改了私有變量score的值盖灸。這里有個問題蚁鳖,原先stu.score=0
這種方式也可以修改score變量,為什么要費這么大周折定義私有變量赁炎,還定義set_score
方法呢醉箕?
在Python中钾腺,通過定義私有變量和對應(yīng)的set方法可以幫助我們做參數(shù)檢查,避免傳入無效的參數(shù)讥裤,如對上面的示例更改如下:
#! /usr/bin/python3
# -*-coding:UTF-8-*-
class Student(object):
def __init__(self, name, score):
self.__name = name
self.__score = score
def info(self):
print('學生:%s放棒;分數(shù): %s' % (self.__name, self.__score))
def get_score(self):
return self.__score
if 0<=score<=100:
self.__score = score
else:
print('請輸入0 到100 的數(shù)字。')
stu = Student('xiaomeng',95)
print('修改前分數(shù):', stu.get_score())
stu.info()
stu.set_score(-10)
print('修改后分數(shù):',stu.get_score())
stu.info()
程序執(zhí)行結(jié)果如下:
修改前分數(shù): 95
學生:xiaomeng己英;分數(shù): 95
請輸入0 到100 的數(shù)字间螟。
修改后分數(shù): 95
學生:xiaomeng;分數(shù): 95
由輸出結(jié)果看到损肛,調(diào)用set_score
方法時厢破,如果傳入的參數(shù)不滿足條件,就按照不滿足條件的程序邏輯執(zhí)行治拿。
既然類有私有變量的說法摩泪,那么類是否有私有方法呢?
答案是肯定的劫谅,類也有私有方法加勤。類的私有方法也是以兩個下劃線開頭,聲明該方法為私有方法同波,且不能在類外使用鳄梅。私有方法的調(diào)用方式如下:
self.__private_methods
我們通過下面的示例進一步了解私有方法的使用:
#! /usr/bin/python3
# -*-coding:UTF-8-*-
class PrivatePublicMethod(object):
def __init__(self):
pass
def __foo(self): # 私有方法
print('這是私有方法')
def foo(self): # 公共方法
print('這是公共方法')
print('公共方法中調(diào)用私有方法')
self.__foo()
print('公共方法調(diào)用私有方法結(jié)束')
pri_pub = PrivatePublicMethod()
print('開始調(diào)用公共方法:')
pri_pub.foo()
print('開始調(diào)用私有方法:')
pri_pub.__foo()
程序執(zhí)行結(jié)果如下:
開始調(diào)用公共方法:
這是公共方法
公共方法中調(diào)用私有方法
這是私有方法
公共方法調(diào)用私有方法結(jié)束
開始調(diào)用私有方法:
Traceback (most recent call last):
File "D:/python/workspace/classdef.py", line 114, in <module>
pri_pub.__foo()
AttributeError: 'PrivatePublicMethod' object has no attribute '__foo'
由輸出結(jié)果看到,私有方法和私有變量類似未檩,不能通過外部調(diào)用戴尸。
繼承
面向?qū)ο缶幊處淼暮锰幹皇谴a的重用,實現(xiàn)重用的方法之一是通過繼承機制冤狡。繼承完全可以理解成類之間類型和子類型的關(guān)系孙蒙。
在面向?qū)ο蟪绦蛟O(shè)計中,當我們定義一個class時悲雳,可以從某個現(xiàn)有的class繼承挎峦,定義的新class稱為子類(Subclass),而被繼承的class稱為****基類合瓢、父類或超類(Base class坦胶、Super class)**。
繼承的定義如下:
class DerivedClassName(BaseClassName):
<statement-1>
.
.
<statement-N>
需要注意:繼承語法class子類名(基類名)時晴楔,//基類名寫在括號里顿苇,基本類是在定義類時,在元組中指明的税弃。
在Python中纪岁,繼承有以下特點:
(1)在繼承中,基類的構(gòu)造方法(__init__()
方法)不會被自動調(diào)用则果,需要在子類的構(gòu)造方法中專門調(diào)用幔翰。
(2)在調(diào)用基類的方法時需要加上基類的類名前綴漩氨,并帶上self參數(shù)變量。區(qū)別于在類中調(diào)用普通函數(shù)時不需要帶self參數(shù)遗增。
(3)在Python中才菠,首先查找對應(yīng)類型的方法,如果在子類中找不到對應(yīng)的方法贡定,才到基類中逐個查找赋访。
例如:
#! /usr/bin/python3
# -*- coding:UTF-8 -*-
class Animal(object):
def run(self):
print('Animal is running...')
上面定義了一個名為Animal的類,類中定義了一個run()
方法直接輸出(沒有顯式定義__init__()
方法缓待,會調(diào)用默認的構(gòu)造方法)蚓耽。在編寫Dog和Cat類時,可以直接從Animal類繼承旋炒,定義如下:
class Dog(Animal):
pass
class Cat(Animal):
pass
在這段代碼片段中步悠,對于Dog來說,Animal就是它的父類瘫镇;對于Animal來說鼎兽,Dog就是它的子類。Cat和Dog類似铣除。
繼承有什么好處谚咬?
繼承最大的好處是子類獲得了父類全部非私有的功能。由于在Animial中定義了非私有的run()方法尚粘,因此作為Animial的子類择卦,Dog和Cat什么方法都沒有定義,自動擁有父類中的run()
方法郎嫁。
執(zhí)行以上代碼:
dog = Dog()
dog.run()
cat = Cat()
cat.run()
程序執(zhí)行結(jié)果如下:
Animal is running...
Animal is running...
由執(zhí)行結(jié)果看到秉继,子類中沒有定義任何方法,但都成功執(zhí)行了run()
方法泽铛。當然尚辑,子類可以擁有一些自己的方法,比如在Dog類中增加一個eat方法:
#! /usr/bin/python3
# -*- coding:UTF-8 -*-
class Dog(Animal):
def eat(self):
print('Eating ...')
dog = Dog()
dog.run()
dog.eat()
以上代碼執(zhí)行結(jié)果如下:
Animal is running...
Eating ...
由執(zhí)行結(jié)果看到盔腔,既執(zhí)行了父類的方法杠茬,又執(zhí)行了自己定義的方法。
子類不能繼承父類中的私有方法铲觉,也不能調(diào)用父類的私有方法澈蝙。父類的定義如下:
#! /usr/bin/python3
# -*- coding:UTF-8 -*-
class Animal(object):
def run(self):
print('Animal is running...')
def __run(self):
print('I am a private method.')
子類定義不變,執(zhí)行如下調(diào)用語句:
dog = Dog()
dog.__run()
執(zhí)行結(jié)果如下:
Traceback (most recent call last):
File "D:/python/workspace/classextend.py", line 25, in <module>
dog.__run()
AttributeError: 'Dog' object has no attribute '__run'
由執(zhí)行結(jié)果看到撵幽,子類不能調(diào)用父類的私有方法,子類雖然繼承了父類礁击,但是調(diào)用父類的私有方法相當于從外部調(diào)用類中的方法盐杂,因而調(diào)用不成功逗载。
對于父類中擴展的非私有方法,子類可以拿來即用链烈,如在父類Animal中增加一個jump方法:
class Animal(object):
def run(self):
print('Animal is running...')
def jump(self):
print('Animal is jumpping....')
def __run(self):
print('I am a private method.')
上面我們增加了一個非私有的jump()
方法厉斟,子類Dog和Cat保持原樣,執(zhí)行如下調(diào)用:
dog = Dog()
dog.run()
dog.jump()
cat = Cat()
cat.run()
cat.jump()
執(zhí)行結(jié)果如下:
Animal is running...
Animal is jumpping....
Animal is running...
Animal is jumpping....
由執(zhí)行結(jié)果看到强衡,子類可以立即獲取父類增加的非私有方法擦秽。
多態(tài)
繼承可以幫助我們重復(fù)使用代碼触幼。但對于繼承中的示例,無論是Dog還是Cat究飞,調(diào)用父類的run()
方法時顯示的都是Animal is running…置谦,如果想讓結(jié)果顯示為Dog is running…和Cat is running…,該怎么處理呢亿傅?
我們對Dog和Cat類做如下改進:
#! /usr/bin/python3
# -*- coding:UTF-8 -*-
class Dog(Animal):
def run(self):
print('Dog is running...')
class Cat(Animal):
def run(self):
print('Cat is running...')
執(zhí)行如下語句:
dog = Dog()
print('實例化Dog 類')
dog.run()
cat = Cat()
print('實例化Cat類')
cat.run()
執(zhí)行結(jié)果如下:
實例化Dog 類
Dog is running...
實例化Cat類
Cat is running...
由執(zhí)行結(jié)果看到媒峡,分別得到了Dog和Cat各自的running結(jié)果。
當子類和父類存在相同的run()方法時葵擎,子類的run()
方法會覆蓋父類的run()
方法丝蹭,在代碼運行時總是會調(diào)用子類的run()
方法,稱之為多態(tài)坪蚁。
多態(tài)來自于希臘語奔穿,意思是有多種形式。多態(tài)意味著即使不知道變量所引用的對象類型是什么敏晤,也能對對象進行操作贱田,多態(tài)會根據(jù)對象(或類)的不同而表現(xiàn)出不同的行為。例如嘴脾,我們在上面的Animal類中定義了run方法男摧,Dog和Cat類分別繼承Animal類,并且分別定義了自己的run方法译打,最后Dog和Cat調(diào)用的是自己定義的run方法耗拓。
為了更好地理解什么是多態(tài),我們對數(shù)據(jù)類型再做一點說明奏司。當我們定義一個類時乔询,實際上就定義了一種數(shù)據(jù)類型。定義的數(shù)據(jù)類型和Python自帶的數(shù)據(jù)類型(如str韵洋、list竿刁、dict)沒什么兩樣黄锤。
a = list() # a 是list 類型
b = Animal() # b 是Animal類型
c = Dog() # c 是Dog 類型
下面用isinstance()
方法判斷一個變量是否是某個類型。
print('a 是否為list類型:', isinstance(a, list))
print('b 是否為Animal 類型:', isinstance(b, Animal))
print('c 是否為Dog 類型:', isinstance(c, Dog))
執(zhí)行結(jié)果如下:
a 是否為list 類型: True
b 是否為Animal類型: True
c 是否為Dog 類型: True
由執(zhí)行結(jié)果看到食拜,a鸵熟、b、c確實分別為list负甸、Animal流强、Dog三種類型。我們再執(zhí)行如下語句:
print('c 是否為Dog 類型:', isinstance(c, Dog))
print('c 是否為Animal 類型:',isinstance(c, Animal))
執(zhí)行結(jié)果如下:
c 是否為Dog 類型: True
c 是否為Animal類型: True
由執(zhí)行結(jié)果看到呻待,c既是Dog類型又是Animal類型打月。這怎么理解呢?
因為Dog是從Animal繼承下來的带污,當我們創(chuàng)建Dog的實例c時僵控,我們認為c的數(shù)據(jù)類型是Dog,但c同時也是Animal, Dog本來就是Animal`的一種鱼冀。
在繼承關(guān)系中报破,如果一個實例的數(shù)據(jù)類型是某個子類,那它的數(shù)據(jù)類型也可以看作是父類千绪。但是反過來就不行充易,例如以下語句:
b = Animal()
print('b 是否為Dog 類型:', isinstance(b, Dog))
執(zhí)行結(jié)果如下:
b 是否為Dog 類型:False
由輸出結(jié)果看到,變量b是Animal的實例化對象荸型,是Animal類型盹靴,但不是Dog類型,也就是Dog可以看成Animal瑞妇,但Animal不可以看成Dog稿静。
我們再看一個示例。編寫一個函數(shù)辕狰,這個函數(shù)接收一個Animal類型的變量改备,定義并執(zhí)行如下函數(shù),執(zhí)行時傳入Animal的實例:
#! /usr/bin/python3
# -*- coding:UTF-8 -*-
def run_two_times(animal):
animal.run()
animal.run()
run_two_times(Animal())
執(zhí)行結(jié)果如下:
Animal is running...
Animal is running...
若執(zhí)行函數(shù)時傳入Dog的實例蔓倍,操作如下:
run_two_times(Dog())
得到執(zhí)行結(jié)果如下:
Dog is running...
Dog is running...
若傳入Cat的實例悬钳,操作如下:
run_two_times(Cat())
得到執(zhí)行結(jié)果如下:
Cat is running...
Cat is running...
看上去沒有什么特殊的地方,已經(jīng)正確輸出預(yù)期結(jié)果了偶翅,但是仔細想想默勾,如果再定義一個Bird類型,也繼承Animal類聚谁,定義如下:
#! /usr/bin/python3
# -*- coding:UTF-8 -*-
class Bird(Animal):
def run(self):
print('Bird is flying the sky...')
run_two_times(Bird())
程序執(zhí)行結(jié)果如下:
Bird is flying the sky...
Bird is flying the sky...
由執(zhí)行結(jié)果我們發(fā)現(xiàn)母剥,新增的Animal子類不必對run_two_times()方法做任何修改。實際上,任何依賴Animal作為參數(shù)的函數(shù)或方法都可以不加修改地正常運行媳搪,原因就在于多態(tài)铭段。
多態(tài)的好處是:當我們需要傳入Dog骤宣、Cat秦爆、Bird等對象時,只需要接收Animal類型就可以了憔披,因為Dog等限、Cat、Bird等都是Animal類型芬膝,按照Animal類型進行操作即可望门。由于Animal類型有run()
方法,因此傳入的類型只要是Animal類或繼承自Animal類锰霜,都會自動調(diào)用實際類型的run()
方法筹误。
多態(tài)的意思是:對于一個變量,我們只需要知道它是Animal類型癣缅,無須確切知道它的子類型厨剪,就可以放心調(diào)用run()
方法。具體調(diào)用的run()方法作用于Animal友存、Dog祷膳、Cat或Bird對象,由運行時該對象的確切類型決定屡立。
多態(tài)真正的威力在于:調(diào)用方只管調(diào)用直晨,不管細節(jié)。當我們新增一種Animal的子類時膨俐,只要確保run()方法編寫正確即可勇皇,不用管原來的代碼是如何調(diào)用的。這就是著名的“開閉”原則:對于擴展開放焚刺,允許新增Animal子類敛摘;對于修改封閉,不需要修改依賴Animal類型的run_two_times()
等函數(shù)檩坚。
很多函數(shù)和運算符都是多態(tài)的着撩,你寫的絕大多數(shù)程序也可能是,即便你并非有意這樣匾委。只要使用多態(tài)函數(shù)和運算符拖叙,多態(tài)就會消除。唯一能夠毀掉多態(tài)的是使用函數(shù)顯式地檢查類型赂乐,如type薯鳍、isinstance函數(shù)等。如果有可能,就盡量避免使用這些毀掉多態(tài)的方式挖滤,重要的是如何讓對象按照我們希望的方式工作崩溪,無論它是否是正確類型或類。
封裝
前面我們講述了Python對象中兩個重點——繼承和多態(tài)斩松,這里將講述第3個重點——封裝伶唯。
封裝是全局作用域中其他區(qū)域隱藏多余信息的原則。聽起來有些像多態(tài)惧盹,使用對象而不用知道其內(nèi)部細節(jié)。它們都是抽象原則钧椰,都會幫忙處理程序組件而不用過多關(guān)心細節(jié)粹断,就像函數(shù)一樣。
封裝并不等同于多態(tài)嫡霞。多態(tài)可以讓用戶對不知道類(或?qū)ο箢愋停┑膶ο筮M行方法調(diào)用瓶埋,而封裝可以不用關(guān)心對象是如何構(gòu)建的,直接使用即可诊沪。
前面幾節(jié)的示例基本都用到封裝的思想养筒,如前面定義的Student類中,每個實例都擁有各自的name和score數(shù)據(jù)娄徊。我們可以通過函數(shù)訪問這些數(shù)據(jù)闽颇,如輸出學生的成績,可以如下定義并執(zhí)行:
#! /usr/bin/python3
# -*- coding:UTF-8 -*-
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
std = Student('xiaozhi',90)
def info(std):
print('學生:%s寄锐;分數(shù): %s' % (std.name, std.score))
info(std)
執(zhí)行結(jié)果為:
學生:xiaozhi兵多;分數(shù): 90
由輸出結(jié)果看到,可以通過函數(shù)調(diào)用類并得到結(jié)果橄仆。
既然Student實例本身就擁有這些數(shù)據(jù)剩膘,要訪問這些數(shù)據(jù)就沒有必要從外面的函數(shù)訪問,可以直接在Student類內(nèi)部定義訪問數(shù)據(jù)的函數(shù)盆顾,這樣就把“數(shù)據(jù)”封裝起來了怠褐。這些封裝數(shù)據(jù)的函數(shù)和Student類本身是相關(guān)聯(lián)的,我們稱之為類的方法您宪。于是就有了前面所寫類的形式:
#! /usr/bin/python3
# -*- coding:UTF-8 -*-
class Student0(object):
def __init__(self, name, score):
self.name = name
self.score = score
def info(self):
print('學生:%s奈懒;分數(shù): %s' % (self.name, self.score))
要定義一個方法,除了第一個參數(shù)是self外宪巨,其他參數(shù)和普通函數(shù)一樣磷杏。要調(diào)用一個方法,在實例變量上直接調(diào)用即可捏卓。除了self不用傳遞极祸,其他參數(shù)正常傳入,執(zhí)行如下語句:
stu = Student0('xiaomeng',95)
執(zhí)行結(jié)果為:
學生:xiaomeng;分數(shù): 95
這樣一來遥金,我們從外部看Student類浴捆,只需要知道創(chuàng)建實例需要給出的name和score,如何輸出是在Student類的內(nèi)部定義的稿械,這些數(shù)據(jù)和邏輯被“封裝”起來了选泻,調(diào)用很容易,但卻不用知道內(nèi)部實現(xiàn)的細節(jié)溜哮。
封裝的另一個好處是可以給Student類增加新方法滔金,比如我們在類的訪問權(quán)限中所講述的get_score()方法和set_score()方法色解。使用這些方法時茂嗓,我們無須知道內(nèi)部實現(xiàn)細節(jié),直接調(diào)用即可科阎。
多重繼承
上面講述的是單繼承述吸,Python還支持多重繼承。多重繼承的類定義如下:
class DerivedClassName(Base1, Base2, Base3):
<statement-1>
.
.
<statement-N>
可以看到锣笨,多重繼承就是有多個基類(父類或超類)蝌矛。
需要注意圓括號中父類的順序,若父類中有相同的方法名错英,在子類使用時未指定入撒,Python會從左到右搜索。若方法在子類中未找到椭岩,則從左到右查找父類中是否包含方法茅逮。
繼續(xù)以前面的Animal類為例,假設(shè)要實現(xiàn)4種動物:Dog(狗)判哥、Bat(蝙蝠)献雅、Parrot(鸚鵡)、Ostrich(鴕鳥)塌计。
如果按照哺乳動物和鳥類分類挺身,我們可以設(shè)計按哺乳動物分類的類層次圖,如圖8-2所示锌仅。如果按照“能跑”和“能飛”分類章钾,我們可以設(shè)計按行為功能分類的類層次圖,如圖所示热芹。
如果要把上面的兩種分類都包含進來贱傀,就得設(shè)計更多層次:
哺乳類:包括能跑的哺乳類和能飛的哺乳類。
鳥類:包括能跑的鳥類和能飛的鳥類剿吻。這么一來窍箍,類的層次就復(fù)雜了。下圖所示為更復(fù)雜的類層次圖。
如果還要增加“寵物類”和“非寵物類”椰棘,類的數(shù)量就會呈指數(shù)增長纺棺,很明顯這樣設(shè)計是不行的。
正確的做法是采用多重繼承邪狞。首先祷蝌,主要的類層次仍按照哺乳類和鳥類設(shè)計,設(shè)計代碼如下:
#! /usr/bin/python3
# -*- coding:UTF-8 -*-
class Animal(object):
pass
# 大類:
class Mammal(Animal):
pass
class Bird(Animal):
pass
# 各種動物:
class Dog(Mammal):
pass
class Bat(Mammal):
pass
class Parrot(Bird):
pass
class Ostrich(Bird):
pass
接下來帆卓,給動物加上Runnable和Flyable功能巨朦。我們先定義好Runnable和Flyable類:
#! /usr/bin/python3
# -*- coding:UTF-8 -*-
class Runnable(object):
def run(self):
print('Running...')
class Flyable(object):
def fly(self):
print('Flying...')
大類定義好后,對需要Runnable功能的動物添加對Runnable的繼承,如Dog:
class Dog(Mammal, Runnable):
pass
對需要Flyable功能的動物添加對Flyable的繼承躏惋,如Bat:
class Bat(Mammal, Flyable):
pass
這樣何之,通過上面的多重繼承,一個子類就可以繼承多個父類棚蓄,同時獲得多個父類所有非私有功能。
獲取對象信息
當我們調(diào)用方法時可能需要傳遞一個參數(shù)碍脏,這個參數(shù)類型我們知道梭依,但是對于接收參數(shù)的方法,就不一定知道是什么參數(shù)類型了典尾。我們該怎么得知參數(shù)的類型呢役拴?
Python為我們提供了以下3種獲取對象類型的方法。
1. 使用type()函數(shù)
我們前面已經(jīng)學習過type()
函數(shù)的使用钾埂,基本類型都可以用type()判斷河闰,例如:
>>> type(123)
<class 'int'>
>>> type('abc')
<class 'str'>
>>> type(None)
<class 'NoneType'>
如果一個變量指向函數(shù)或類,用type()
函數(shù)返回的是什么類型勃教?在交互模式下輸入:
>>> type(abs)
<class 'builtin_function_or_method'>
>>> type(pri_pub) #上面定義的PrivatePublicMethod 類
<class '__main__.PrivatePublicMethod'>
由輸出結(jié)果看到淤击,返回的是對應(yīng)的Class類型。
如果我們要在if語句中判斷并比較兩個變量的type類型是否相同故源,應(yīng)如下操作:
>>> type(123)==type(456)
True
>>> type(123)==int
True
>>> type('abc')==type('123')
True
>>> type('abc')==str
True
>>> type('abc')==type(123)
False
通過操作我們看到污抬,判斷基本數(shù)據(jù)類型可以直接寫int、str等绳军。怎么判斷一個對象是否是函數(shù)呢印机?
可以使用types模塊中定義的常量,在交互模式下輸入:
>>> import types
>>> def func():
... pass
...
>>> type(fn)==types.FunctionType
True
>>> type(abs)==types.BuiltinFunctionType
True
>>> type(lambda x: x)==types.LambdaType
True
>>> type((x for x in range(10)))==types.GeneratorType
True
由執(zhí)行結(jié)果看到门驾,函數(shù)的判斷方式需要借助types模塊的幫助射赛。
2. 使用isinstance()函數(shù)
要明確class的繼承關(guān)系,使用type()很不方便奶是,通過判斷class的數(shù)據(jù)類型確定class的繼承關(guān)系要方便得多楣责,這個時候可以使用isinstance()
函數(shù)竣灌。
例如,繼承關(guān)系是如下形式:
object -> Animal -> Dog
即Animal繼承object秆麸、Dog繼承Animal初嘹。使用isinstance()可以告訴我們一個對象是否是某種類型。
例如沮趣,創(chuàng)建如下兩種類型的對象:
>>> animal = Animal()
>>> dog = Dog()
對上面兩種類型的對象屯烦,使用isinstance進行判斷:
>>> isinstance(dog, Dog)
True
根據(jù)輸出結(jié)果看到,dog是Dog類型房铭,這個沒有任何疑問驻龟,因為dog變量指向的就是Dog對象。接下來判斷Animal類型缸匪,使用isinstance判斷如下:
>>> isinstance(dog, Animal)
True
根據(jù)輸出結(jié)果看到翁狐,dog也是Animal類型。
由此我們得知:盡管dog是Dog類型豪嗽,不過由于Dog是從Animal繼承下來的谴蔑,因此dog也是Animal類型。換句話說龟梦,isinstance()
判斷的是一個對象是否為該類型本身,或者是否為該類型繼承類的類型窃躲。
我們可以確信计贰,dog還是object類型:
>>> isinstance(dog, object)
True
同時確信,實際類型是Dog類型的dog蒂窒,同時也是Animal類型:
>>> isinstance(dog, Dog) and isinstance(dog, Animal)
True
不過animal不是Dog類型躁倒,這個我們在8.5節(jié)已經(jīng)講述過:
>>> isinstance(animal,Dog )
False
提醒一點,能用type()判斷的基本類型也可以用isinstance()判斷洒琢。這個可以自己進行驗證秧秉。
isinstance()
可以判斷一個變量是否為某些類型中的一種,判斷變量是否為list或tuple的方式如下:
>>> isinstance([1, 2, 3], (list, tuple))
True
>>> isinstance((1, 2, 3), (list, tuple))
True
3. 使用dir()
如果要獲得一個對象的所有屬性和方法衰抑,就可以使用dir()
函數(shù)象迎。dir()
函數(shù)返回一個字符串的list。例如呛踊,獲得一個str對象的所有屬性和方法的方式如下:
>>> dir('abc')
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__',
'__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__',
'__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__',
'__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode',
'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isdecimal', 'isdigit', 'isidentifier',
'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition',
'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title',
'translate', 'upper', 'zfill']
由輸出結(jié)果看到砾淌,str對象包含許多屬性和方法。
類的專有方法
我們前面講述了類的訪問權(quán)限谭网、私有變量和私有方法汪厨,除了自定義私有變量和方法外,Python類還可以定義專有方法愉择。專有方法是在特殊情況下或使用特別語法時由Python調(diào)用的劫乱,而不是像普通方法一樣在代碼中直接調(diào)用织中。本節(jié)講述幾個Python常用的專有方法。
看到形如__xxx__
的變量或函數(shù)名就要注意衷戈,這在Python中是有特殊用途的抠璃。
__init__
我們已經(jīng)知道怎么用了,Python的class中有許多這種有特殊用途的函數(shù)脱惰,可以幫助我們定制類搏嗡。下面介紹這種特殊類型的函數(shù)定制類的方法。
1.__str__
開始介紹之前拉一,我們先定義一個Student類采盒,定義如下:
#! /usr/bin/python3
# -*- coding:UTF-8 -*-
class Student(object):
def __init__(self, name):
self.name = name
print(Student('xiaozhi'))
執(zhí)行結(jié)果如下:
<__main__.Student object at 0x0000000000D64198>
執(zhí)行結(jié)果輸出一堆字符串,一般人看不懂蔚润,沒有什么可用性磅氨,也不好看。怎樣才能輸出得好看呢嫡纠?
只需要我們定義好__str__()
方法烦租,返回一個好看的字符串就可以了。重新定義上面的示例:
#! /usr/bin/python3
# -*- coding:UTF-8 -*-
class Student(object):
def __init__(self, name):
self.name = name
def __str__(self):
return '學生名稱: %s' % self.name
print(Student('xiaozhi'))
執(zhí)行結(jié)果為:
學生名稱: xiaozhi
由執(zhí)行結(jié)果看到除盏,這樣輸出的實例不但好看叉橱,而且是我們想要的。
如果在交互模式下輸入如下:
>>> s = Student('xiaozhi')
>>> s
<__main__.Student object at 0x00000000030EC550>
由執(zhí)行結(jié)果看到者蠕,輸出的實例還跟之前一樣窃祝,不容易識別。
這是因為直接顯示變量調(diào)用的不是__str__()
踱侣,而是__repr__()
粪小,兩者的區(qū)別在于__str__()
返回用戶看到的字符串,而__repr__()
返回程序開發(fā)者看到的字符串抡句。也就是說探膊,__repr__()
是為調(diào)試服務(wù)的。
解決辦法是再定義一個__repr__()
待榔。通常逞壁,__str__()
和__repr__()
代碼是一樣的,所以有一個偷懶的寫法:
#! /usr/bin/python3
# -*- coding:UTF-8 -*-
class Student(object):
def __init__(self, name):
self.name = name
def __str__(self):
return '學生名稱: %s' % self.name
__repr__ = __str__
在交互模式下執(zhí)行:
>>> s = Student('xiaozhi')
>>> s
學生名稱: xiaozhi
可以看到究抓,已經(jīng)得到滿意的結(jié)果了猾担。
2.__iter__
如果想將一個類用于for ... in循環(huán),類似list或tuple一樣刺下,就必須實現(xiàn)一個__iter__()
方法绑嘹。該方法返回一個迭代對象,Python的for循環(huán)會不斷調(diào)用該迭代對象的__next__()
方法橘茉,獲得循環(huán)的下一個值工腋,直到遇到StopIteration錯誤時退出循環(huán)姨丈。
我們以斐波那契數(shù)列為例,寫一個可以作用于for循環(huán)的Fib類:
#! /usr/bin/python3
# -*- coding:UTF-8 -*-
class Fib(object):
def __init__(self):
self.a, self.b = 0, 1 # 初始化兩個計數(shù)器a擅腰、b
def __iter__(self):
return self # 實例本身就是迭代對象蟋恬,故返回自己
def __next__(self):
self.a, self.b = self.b, self.a + self.b # 計算下一個值
if self.a > 100000: # 退出循環(huán)的條件
raise StopIteration();
return self.a # 返回下一個值
下面我們把Fib實例作用于for循環(huán)。
>>> for n in Fib():
... print(n)
...
1
1
2
3
5
. . .
89
3. __getitem__
Fib實例雖然能夠作用于for循環(huán)趁冈,和list有點像歼争,但是不能將它當成list使用。比如取第3個元素:
>>> Fib()[3]
Traceback (most recent call last):
File "<pyshell#35>", line 1, in <module>
Fib()[3]
TypeError: 'Fib' object does not support indexing
由執(zhí)行結(jié)果看到渗勘,取元素時報錯了沐绒。怎么辦呢?
要像list一樣按照下標取出元素旺坠,需要實現(xiàn)__getitem__()
方法乔遮,代碼如下:
class Fib(object):
def __getitem__(self, n):
a, b = 1, 1
for x in range(n):
a, b = b, a + b
return a
下面嘗試取得數(shù)列的值:
>>> fib = Fib()
>>> fib[3]
3
>>> fib[10]
89
由執(zhí)行結(jié)果看到,可以成功獲取對應(yīng)數(shù)列的值了取刃。
4. __getattr__
正常情況下蹋肮,調(diào)用類的方法或?qū)傩詴r,如果類的方法或?qū)傩圆淮嬖诰蜁箦e璧疗。比如定義Student類:
class Student(object):
def __init__(self, name):
self.name = 'xiaozhi'
對于上面的代碼坯辩,調(diào)用name屬性不會有任何問題,但是調(diào)用不存在的score屬性就會報錯病毡。
執(zhí)行以下代碼:
>>> stu = Student()
>>> print(stu.name)
Xiaozhi
>>> print(stu.score)
Traceback (most recent call last):
File "<pyshell#50>", line 1, in <module>
print(stu.score)
AttributeError: 'Student' object has no attribute 'score'
由輸出結(jié)果看到濒翻,錯誤信息告訴我們沒有找到score屬性。對于這種情況啦膜,有什么解決方法嗎?
要避免這個錯誤淌喻,除了可以添加一個score屬性外僧家,Python還提供了另一種機制,就是寫一個__getattr__()
方法裸删,動態(tài)返回一個屬性八拱。上面的代碼修改如下:
class Student(object):
def __init__(self):
self.name = 'xiaozhi'
def __getattr__(self, attr):
if attr=='score':
return 95
當調(diào)用不存在的屬性時(如score),Python解釋器會調(diào)用__getattr__(self, 'score')
嘗試獲得屬性涯塔,這樣就有機會返回score的值肌稻。在交互模式下輸入如下:
>>> stu = Student()
>>> stu.name
xiaozhi
>>> stu.score
95
由輸出結(jié)果看到,可以正確輸出不存在的屬性的值了匕荸。
注意爹谭,只有在沒有找到屬性的情況下才調(diào)用__getattr__
,已有的屬性(如name)榛搔,不會在__getattr__
中查找诺凡。此外东揣,如果所有調(diào)用都會返回None(如stu.abc),就是定義的__getattr__
默認返回None腹泌。
5. __call__
一個對象實例可以有自己的屬性和方法嘶卧,調(diào)用實例的方法時使用instance.method()
調(diào)用。能不能直接在實例本身調(diào)用呢凉袱?答案是可以芥吟。
任何類,只需要定義一個__call__()
方法专甩,就可以直接對實例進行調(diào)用钟鸵,例如:
class Student(object):
def __init__(self, name):
self.name = name
def __call__(self):
print('名稱:%s' % self.name)
在交互模式下輸入如下:
>>> stu = Student('xiaomeng')
>>> stu()
名稱:xiaomeng
由輸出結(jié)果看到,可以直接對實例進行調(diào)用并得到結(jié)果配深。
__call__()
還可以定義參數(shù)携添。對實例進行直接調(diào)用就像對一個函數(shù)調(diào)用一樣,完全可以把對象看成函數(shù)篓叶,把函數(shù)看成對象烈掠,因為這兩者本來就有根本區(qū)別。
如果把對象看成函數(shù)缸托,函數(shù)本身就可以在運行期間動態(tài)創(chuàng)建出來左敌,因為類的實例都是運行期間創(chuàng)建出來的。這樣一來俐镐,就模糊了對象和函數(shù)的界限矫限。
怎么判斷一個變量是對象還是函數(shù)呢?
很多時候判斷一個對象是否能被調(diào)用佩抹,可以使用Callable()函數(shù)叼风,比如函數(shù)和上面定義的帶有__call__()
的類實例。輸入如下:
>>> callable(Student('xiaozhi'))
True
>>> callable(max)
True
>>> callable([1, 2, 3])
False
>>> callable(None)
False
>>> callable('a')
False
由操作結(jié)果看到棍苹,通過callable()函數(shù)可以判斷一個對象是否為“可調(diào)用”對象无宿。
牛刀小試——出行建議
小智今天想出去,但不清楚今天的天氣是否適宜出行枢里,需要一個幫他提供建議的程序孽鸡,程序要求輸入daytime和night,根據(jù)可見度和溫度給出出行建議和使用的交通工具栏豺,需要考慮需求變更的可能彬碱。
需求分析:使用本章所學的封裝、繼承奥洼、多態(tài)比較容易實現(xiàn)巷疼,由父類封裝查看可見度和查看溫度的方法,子類繼承父類溉卓。若有需要皮迟,子類可以覆蓋父類的方法搬泥,做自己的實現(xiàn)。子類也可以自定義方法伏尼。
定義天氣查找類忿檩,類中定義兩個方法,一個方法根據(jù)傳入的input_daytime值返回對應(yīng)的可見度爆阶;另一個方法根據(jù)傳入的input_daytime值返回對應(yīng)的溫度燥透。
#! /usr/bin/python3
# -*- coding:UTF-8 -*-
class WeatherSearch(object):
def __init__(self, input_daytime):
self.input_daytime = input_daytime
def seach_visibility(self):
visible_leave = 0
if self.input_daytime == 'daytime':
visible_leave = 2
if self.input_daytime == 'night':
visible_leave = 9
return visible_leave
def seach_temperature(self):
temperature = 0
if self.input_daytime == 'daytime':
temperature = 26
if self.input_daytime == 'night':
temperature = 16
return temperature
定義建議類,該類繼承WeatherSearch類辨图。類中定義兩個方法班套,一個覆蓋父類的溫度查找方法,具有傳入的input_daytime
的值故河,返回建議使用的交通工具吱韭;另一個方法返回整體的建議。
#! /usr/bin/python3
# -*- coding:UTF-8 -*-
class OutAdvice(WeatherSearch):
def __init__(self, input_daytime):
WeatherSearch.__init__(self, input_daytime)
def seach_temperature(self):
vehicle = ''
if self.input_daytime == 'daytime':
vehicle = 'bike'
if self.input_daytime == 'night':
vehicle = 'taxi'
return vehicle
def out_advice(self):
visible_leave = self.seach_visibility()
if visible_leave == 2:
print('The weather is good,suitable for use %s.' % self.seach_temperature())
elif visible_leave == 9:
print('The weather is bad,you should use %s.' % self.seach_temperature())
else:
print('The weather is beyond my scope,I can not give you any advice')
程序調(diào)用如下:
check = OutAdvice('daytime')
check.out_advice()
結(jié)果如下:
The weather is good,suitable for use bike.
調(diào)試
在程序運行的任何時刻為對象添加屬性都是合法的鱼的,不過應(yīng)當避免讓對象擁有相同的類型卻有不同的屬性組理盆。
在init方法中初始化對象的全部屬性是一個好習慣,可以幫助你用戶更好地管理類中的屬性和對屬性值的更改凑阶。
繼承會給調(diào)試帶來新挑戰(zhàn)猿规,因為當你調(diào)用對象的方法時,可能無法知道調(diào)用的是哪一個方法宙橱。一旦無法確認程序的運行流程姨俩,最簡單的解決辦法是在適當位置添加一個輸出語句,如在相關(guān)方法的開頭或方法調(diào)用開始處等师郑。