15 Python面向?qū)ο缶幊?/h1>

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ù)因谎,豈不是很混亂。
如果外部代碼要獲取類中的namescore怎么辦呢颜懊?
在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é)果看到强衡,子類可以立即獲取父類增加的非私有方法擦秽。

繼承可以一級一級繼承下來,就好比從爺爺?shù)桨职衷俚絻鹤拥年P(guān)系漩勤。所有類最終都可以追溯到根類object感挥,這些繼承關(guān)系看上去就像一顆倒著的樹,如圖所示越败。
繼承樹

多態(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ù)雜的類層次圖。
更復(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()可以判斷一個變量是否為某些類型中的一種,判斷變量是否為listtuple的方式如下:

>>> 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)用開始處等师郑。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者

  • 序言:七十年代末环葵,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子宝冕,更是在濱河造成了極大的恐慌积担,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件猬仁,死亡現(xiàn)場離奇詭異,居然都是意外死亡先誉,警方通過查閱死者的電腦和手機湿刽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來褐耳,“玉大人诈闺,你說我怎么就攤上這事×迓” “怎么了雅镊?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵襟雷,是天一觀的道長。 經(jīng)常有香客問我仁烹,道長耸弄,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任卓缰,我火速辦了婚禮计呈,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘征唬。我一直安慰自己捌显,他們只是感情好,可當我...
    茶點故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布总寒。 她就那樣靜靜地躺著扶歪,像睡著了一般。 火紅的嫁衣襯著肌膚如雪摄闸。 梳的紋絲不亂的頭發(fā)上善镰,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天,我揣著相機與錄音贪薪,去河邊找鬼媳禁。 笑死,一個胖子當著我的面吹牛画切,可吹牛的內(nèi)容都是我干的竣稽。 我是一名探鬼主播,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼霍弹,長吁一口氣:“原來是場噩夢啊……” “哼毫别!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起典格,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤岛宦,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后耍缴,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體砾肺,經(jīng)...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年防嗡,在試婚紗的時候發(fā)現(xiàn)自己被綠了变汪。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,727評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡蚁趁,死狀恐怖裙盾,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤番官,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布庐完,位于F島的核電站,受9級特大地震影響徘熔,放射性物質(zhì)發(fā)生泄漏门躯。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一近顷、第九天 我趴在偏房一處隱蔽的房頂上張望生音。 院中可真熱鬧,春花似錦窒升、人聲如沸缀遍。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽域醇。三九已至,卻和暖如春蓉媳,著一層夾襖步出監(jiān)牢的瞬間譬挚,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工酪呻, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留减宣,地道東北人。 一個月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓玩荠,卻偏偏與公主長得像漆腌,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子阶冈,可洞房花燭夜當晚...
    茶點故事閱讀 44,619評論 2 354

推薦閱讀更多精彩內(nèi)容