官术网_书友最值得收藏!

2.5.7 Python的面向對象

Python從設計之初就已經是一門面向對象的語言,正因為如此,在Python中創建一個類和對象是很容易的。本章將詳細介紹Python的面向對象編程。

Python是支持面向對象、面向過程、函數式編程等多種編程范式的,它不強制我們使用任何一種編程范式,我們可以使用過程式編程編寫任何程序。對于中等和大型項目來說,面向對象將給我們帶來許多優勢。如果你以前沒有接觸過面向對象的編程語言,那可能需要先了解一些面向對象語言的基本特征,在頭腦里形成一個基本的面向對象的概念,這樣有助于你更容易地學習Python的面向對象編程。

下面來簡單了解一下面向對象的基本特征。

1.面向對象的基本定義

Python面向對象的基本定義如下。

·類(Class):用來描述具有相同屬性和方法的對象的集合。它定義了該集合中每個對象所共有的屬性和方法。對象是類的實例。

·類變量:類變量在整個實例化的對象中是公用的。類變量定義在類中且在函數體之外。類變量通常不作為實例變量使用。

·方法:類中定義的函數。

·數據成員:類變量或者實例變量用于處理類及其實例對象的相關數據。

·方法重寫:如果從父類繼承的方法不能滿足子類的需求,可以對其進行改寫,這個過程叫方法的覆蓋(override),也稱為方法的重寫。

·實例變量:定義在方法中的變量,只作用于當前實例的類。

·繼承:即一個派生類(derived class)繼承基類(base class)的字段和方法。繼承也允許把一個派生類的對象作為一個基類對象對待。

·實例化:創建一個類的實例、類的具體對象。

·對象:通過類定義的數據結構實例。對象包括兩個數據成員(類變量和實例變量)和一個方法。

類和對象是面向對象的兩個重要概念,類是客觀世界中事物的抽象,而對象則是類實例化后的實體。大家可以將類想象成圖紙或模型,對象則是通過圖紙或模型設計出來的實物。例如,同樣的汽車模型可以造出不同的汽車,不同的汽車有不同的顏色、價格和車牌,如圖2-7所示。

圖2-7 以汽車類型類比類和實例化

汽車模型是對汽車特征和行為的抽象,而汽車則是實際存在的事物,是客觀世界中實實在在的物體。

我們在描述一個真實對象(物體)時包括以下兩個方面:

·它可以做什么(行為)。

·它是什么樣的(屬性或特征)。

在Python中,一個對象的特征也稱為屬性,它所具有的行為則稱為方法,對象=屬性+方法。另外在Python中,我們會把具有相同屬性和方法的對象歸為一個類。

這里舉個簡單的例子:


#-*- encoding:utf8 -*-  
class Turtle(object):
    #屬性
    color = "green"
    weight = "10"
    #方法
    def run(self):
        print "我正在跑..."
    def sleep(self):
        print "我正在睡覺..."

tur = Turtle()
print tur.weight
#打印實例tur的weight屬性
tur.sleep()
#調用實例tur的sleep方法

執行后的結果如下:


10
我正在睡覺...

Python會自動給每個對象添加特殊變量self,它相當于C++的指針,這個變量指向對象本身,讓類中的函數能夠明確地引用對象的數據和函數(self不能被忽略),示例如下:


#-*- encoding:utf-8 -*-

class NewClass(object):
    def __init__(self,name):
        print self
        self.name = name 
        print "我的名字是:{}".format(self.name)

cc = NewClass('yhc')

打印結果如下:


<__main__.NewClass instance at 0x020D4440>
我的名字是:yhc

format函數是Python新增的一種格式化字符串的函數,它增強了字符串格式化的功能。其基本語法是通過“{}”和“:”來代替以前的“%”,可以接受無限個參數,位置可以不按順序排列。

在這段代碼中,self是NewClass類在內存地址0x020D4440處的實例。因此,self在這里與C++中的this一樣,代表的都是當前對象的地址,可以用來調用當前類中的屬性和方法。在這段代碼中,有一個特殊的函數,即__init__()方法,它是Python中的構造函數,構造函數用于初始化類的內部狀態,為類的屬性設置默認值。

如果我們想看一下cc的屬性,可以在Python命令行模式下輸入如下命令:


dir(cc)

打印結果如下:


['__class__',
 '__delattr__',
 '__dict__',
 '__doc__',
 '__format__',
 '__getattribute__',
 '__hash__',
 '__init__',
 '__module__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 '__weakref__',
 'name']

內建函數dir()可以顯示類屬性,同樣還可以打印所有的實例屬性。

與類相似,實例其實也有一個__dict__的特殊屬性,它是由實例屬性構成的一個字典,同樣在Python命令行模式下輸入如下命令:


cc.__dict__

輸出結果如下:


{'name': 'yhc'}

事實上,Python中定義了很多內置類屬性,用于管理類的內部關系。

·__dict__:類的屬性(包含一個字典,由類的數據屬性組成)。

·__doc__:類的文檔字符串。

·__name__:類名。

·__module__:類定義所在的模塊(類的全名是'__main__.className',如果類位于一個導入模塊mymod中,那么className.__module__等于mymod)。

·__bases__:類的所有父類構成元素(包含了一個由所有父類組成的元組)。

我們如果執行print NewClass.__bases__這段代碼,則會輸出如下結果:


(<type 'object'>,)

另外,在上面的代碼中,如果想打印出cc的值,可用如下命令:


print cc

打印結果如下:


<__main__.NewClass instance at 0x020D4440>

在這里,cc跟上面的self的效果是一樣的,它也是NewClass類在內存地址0x020D4440處的實例,顯然這種不是我們想要的效果,所以需要一個方法來打印出適合我們人類閱讀的方式,這里采用__str__,將上面的代碼精簡并加入新的內容,整個代碼變成:


# -*- coding: UTF-8 -*-

class NewClass(object):
    def __init__(self,name):
        # print self
        self.name = name 
        print "我的名字是:{}".format(self.name)
    def __str__(self):
        print "NewClass:{}".format(self.name)

cc = NewClass('yhc')

注意,對于這里采用的__str__方法,它的輸出結果為我們預先定義好的格式:


我的名字是:yhc

__repr__具有跟__str__類似的效果,這里就不重復演示了。事實上,我們在創建自己的類和對象時,編寫__str__和__repr__方法是有必要的。它們對于顯示對象的內容很有幫助,而顯示對象內容有助于調試程序。

注意

__str__()必須使用return語句返回,如果__str__()不返回任何值,則執行print語句會出錯。

另外,請注意這段代碼中的object,即class NewClass(object),專業的說法叫定義基類。很多資料上面都將此object略過了,這里寫段代碼對比一下帶上object和不帶object的區別:


class NewClass():
    pass
class NewClass1(object):
    pass

a1 = NewClass()
print dir(a1)
a2 = NewClass1()
print dir(a2)

執行這段代碼,發現區別還是很明顯的:


['__doc__', '__module__']
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']

還可以用__bases__類屬性來看一下NewClass和NewClass1的區別,代碼如下:


print NewClass.__bases__
print NewClass1.__bases__

結果如下:


()
(<type 'object'>,)

NewClass和NewClass1類的區別很明顯,NewClass不繼承object對象,只擁有了doc和module,也就是說這個類的命名空間只有兩個對象可以操作;而NewClass1類繼承了object對象,擁有好多可操作對象,這些都是類中的高級特性。另外,此處如果不加object,有時候還會影響代碼的執行結果,所以結合以上種種因素考慮,建議此處帶上object。

注意

Python 2.7中默認都是經典類,只有顯式繼承了object才是新式類,即類名后面括號中需要帶上object;Python 3.7(Python3.x)中默認都是新式類,不必顯式地繼承object。由于本書采用的版本是Python 2.7.10,因此建議此處都帶上object。

2.Python裝飾器

Python面向對象的開發工作中經常會涉及Python裝飾器,它究竟有什么用途呢?

裝飾器本質上是一個Python函數,它可以讓其他函數不需要做任何代碼變動即可增加額外的功能,裝飾器的返回值也是一個函數對象。它經常用于有切面需求的場景,比如:插入日志、性能測試、事務處理、緩存、權限校驗等。裝飾器是解決這類問題的絕佳設計,有了裝飾器,我們就可以抽離出大量與函數功能本身無關的雷同代碼并繼續重用。概括地講,裝飾器的作用就是為已經存在的對象添加額外的功能。

這里先來看一個簡單的例子:


def foo():
    print('i am foo')

現在有一個新的需求,即記錄下函數的執行日志,于是在代碼中添加了日志代碼:


import logging
def foo():
    print "i am foo"
    logging.info "foo is running"

那么問題來了,foo1()、foo2()也有類似的需求,怎么做?再寫一個logging在foo1和foo2函數里?這樣就會造成大量雷同的代碼,為了減少重復寫代碼,我們可以這樣做,重新定義一個函數專門處理日志,日志處理完之后再執行真正的業務代碼,示例如下:


import logging
def use_logging(func):
    logging.warn("{} is running".format(func.__name__))
    func()
def bar():
    print "i am bar"
use_logging(bar)

邏輯上不難理解,但是這樣做的話,我們每次都要將一個函數作為參數傳遞給use_logging函數,而且這種方式已經破壞了原有的代碼邏輯結構,之前執行業務邏輯時,運行bar()即可,但是現在不得不改成use_logging(bar)。那么有沒有更好的處理方式呢?當然有,答案就是使用Python裝飾器,示例代碼如下:


import logging
def use_logging(func):
    def wrapper(*args, **kwargs):
        logging.warn("{} is running".format(func.__name__))
        return func(*args, **kwargs)
    return wrapper

def bar():
    print "i am bar"

bar = use_logging(bar)
bar()

函數use_logging就是裝飾器,它把執行真正業務方法的func包裹在函數里面,看起來像bar被use_logging裝飾了。

下面來介紹一下Python程序中出現的@符號。

@符號是裝飾器的語法糖(也是Python獨有的語法糖,其他語言中沒有),在定義函數的時候使用,可避免再一次的賦值操作。也就是說,可以省去bar=use_logging(bar)這一句,直接調用bar()即可得到想要的結果。如果我們有其他的類似函數,可以繼續調用裝飾器來修飾函數,而不用重復修改函數或者增加新的封裝。這樣就提高了程序的可重復利用性,并增加了程序的可讀性。程序如下:


def use_logging(func):
    def wrapper(*args, **kwargs):
        logging.warn("{} is running".format(func.__name__))
        return func(*args)
    return wrapper
@use_logging
def foo1():
    print "i am foo"
@use_logging
def foo2():
    print "i am bar"
foo1()
foo2()

在Python中使用裝飾器如此方便,這要歸因于Python的函數能像普通的對象一樣作為參數傳遞給其他函數,它可以被賦值給其他變量,可以作為返回值,可以被定義在另外一個函數內。@staticmethod和@classmethod這些裝飾器在面向對象的開發工作中會經常用到,希望大家都能熟練掌握其用法。

3.面向對象的特性介紹

面向對象的編程帶來的主要好處之一是代碼的復用,實現這種復用的方法之一是使用繼承機制。繼承完全可以理解成類之間類型和子類型的關系。

注意,繼承的語法為class派生類名(基類名)…,基類名要寫在括號里,基本類是在類定義的時候,在元組中指明的。

在Python的面向對象中,繼承機制具有如下特點:

·在繼承中基類的構造方法__init__()方法不會被自動調用,它需要在其派生類的構造中親自調用。

·在調用基類的方法時,需要加上基類的類名前綴,且需要帶上self參數變量。但在類中調用普通函數時并不需要帶上self參數。

·Python總是會首先查找對應類型的方法,如果不能在派生類中找到,才會到基類中逐個查找(先在本類中查找調用的方法,找不到才去基類中找)。

·如果在繼承元組中列了一個以上的類,那么它就被稱作“多重繼承”,也稱為“Mixin”。

類的繼承語法為:


classname(parent_class1,parent_class2,prant_class3...)

這里舉個簡單的例子說明其用法,GoldFish類繼承自Fish父類,其繼承關系如圖2-8所示。

圖2-8  Python類繼承關系圖示

完整的代碼如下:


#-*- coding:utf-8 -*-

class Fish(object):
    def __init__(self,name):
        self.name = name
        print "我是一條魚"

class GoldFish(Fish):
    def __init__(self,name):
        Fish.__init__(self,name) #顯式調用父類的構造函數
        print "我不僅是條魚,還是條金魚"

if __name__ == "__main__":
    aa = Fish('fish')
    bb = GoldFish('goldfish')

輸出結果如下:


我是一條魚
我是一條魚
我不僅是條魚,還是條金魚

可以看到,GoldFish類成功地繼承了Fish父類。

在工作中常會遇到在子類里訪問父類的同名屬性,而又不想直接引用父類名字的情況,因為說不定什么時候會去修改它,所以數據還是只保留一份的好。這時可以采用super()的方式,其語法為:


super(type,object)

type一般接的是父類的名稱,object接的是self,示例如下:


#-*- coding:utf-8 -*-
class Fruit(object):
    def __init__(self,name):
        self.name = name
    def greet(self):
        print "我的種類是:{}".format(self.name)

class Banana(Fruit):
    def greet(self):
        super(Banana,self).greet()
        print "我是香蕉,在使用super函數"

if __name__ == "__main__":
    aa = Fruit('fruit')
    aa.greet()


    cc = Banana('banana')
    cc.greet()

輸出結果如下:


我的種類是:fruit
我的種類是:banana
我是香蕉,在使用super函數

Banana類在這里也繼承了父類Fruit類。此外,在繼承父類的同時,子類也可以重寫父類的方法,這叫方法重寫,示例如下:


class Fruit(object):
    def __init__(self,color):
        self.color = color 
        print "fruit's color %s:" % self.color

    def grow(self):
        print "grow ..."

class Apple(Fruit):
    def __init__(self,color):
        Fruit.__init__(self,color)
        print "apple's clolr {}:".format(self.color)

    def grow(self):
        print "sleep ..."

if __name__ == "__main__":
    apple = Apple('red')
    apple.grow()

程序執行結果如下:


fruit's color red:
apple's clolr red:
sleep ...

另外,通過繼承,我們可以獲得另一個好處:多態。

多態的好處就是,當我們需要傳入更多的子類,例如新增Teenagers、Grownups等時,只需要繼承Person類型就可以了,而print_title()方法既可以不重寫(即使用Person的),也可以重寫一個特有的。調用方只管調用,不管細節,而當我們新增一種Person的子類時,只要確保新方法編寫正確即可,不用管原來的代碼,這就是著名的“開閉”原則。

·對擴展開放(Open for extension):允許子類重寫方法函數。

·對修改封閉(Closed for modification):不重寫,直接繼承父類方法函數。

來看個示例:


#!/usr/bin/env python
# -*- encoding:utf-8 -*-

class Fruit(object):
    def __init__(self,color = None):
        self.color = color

class Apple(Fruit):
    def __init__(self,color = 'red'):
        Fruit.__init__(self,color)

class Banana(Fruit):
    def __init__(self,color = "yellow"):
        Fruit.__init__(self,color)

class FruitShop:
    def sellFruit(self,fruit):
        if isinstance(fruit,Apple):
            print "sell apple"
        if isinstance(fruit,Banana):
            print "sell banana"
        if isinstance(fruit,Fruit):
            print "sell fruit"

if __name__ == "__main__":
    shop = FruitShop()
    apple = Apple("red")
    banana = Banana('yellow')
    shop.sellFruit(apple)
    #Python的多態性,傳遞apple
    shop.sellFruit(banana)
    #Python的多態性,傳遞banana

代碼執行結果如下:


sell apple
sell fruit
sell banana
sell fruit

多重繼承(也稱為Mixin)跟其他主流語言一樣,Python也支持多重繼承,多重繼承雖然有不少好處,但是問題其實也很多,比如存在屬性繼承等問題,所以我們設計Python多重繼承的時候,應盡可能地讓代碼邏輯簡單明了,這里簡單說明Python多重繼承的用法,示例如下:


class A(object):
    def foo(self):
        print('called A.foo()')

class B(A):
    pass

class C(A):
    def foo(self):
        print('called C.foo()')

class D(B, C): 
    pass

if __name__ == '__main__':
    d = D() 
    d.foo()
    print D.__bases__

在上述代碼中,B、C是A的子類,D繼承了B和C兩個類,其中C重寫了A中的foo()方法。

輸出結果如下:


called C.foo()
(<class '__main__.B'>, <class '__main__.C'>)

請注意最后一行,這說明D隸屬于父類B和C,事實上我們還可以用issubclass()函數來判斷,其語法為:


issubclass(sub,sup)

issubclass()返回True的情況為給出的子類屬于父類(父類這里也可以是一個元組)的一個子類(反之,則為False),命令如下:


issubclass(D,(B,C))

在命令行下輸入上面命令,則返回結果為:


True

另外我們還可以用isinstance()函數來判斷對象是否是類的實例,語法如下:


isinstance(obj,class)

如果對象obj是class類的一個實例或其子類的一個實例,會返回True;反之,則返回False。

最后還得提一下多重繼承的MRO(方法解釋順序),我們在寫類繼承時都會帶上object類,它采用的是C3算法(類似于廣度優先,如果不帶上object,它采取的就是深度優先算法,所以為了避免程序的差異性,這里所有基于class類的寫法均帶上了object類),下面用示例來分析其用法:


class A(object):
    def getValue(self):
        print 'return value of A'
    def show(self):
        print 'I can show the information of A'

class B(A):
    def getValue(self):
        print 'return value of B'

class C(A):
    def getValue(self):
        print 'return value of B'
    def show(self):
        print 'I can show the information of C'
class D(B,C):
    pass


d = D()
d.show()
d.getValue()

輸出結果如下:


I can show the information of C
return value of B

用下面的命令打印D類的__mro__屬性:


print D.__mro__

結果如下:


(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)

從結果可以看出,其繼承順序為D→B→C→A。

注意

事實上,在Python面向對象的開發工作中,我們應該盡量避免采用多重繼承。除此之外,還要注意不要混用經典類和新式類,調用父類的時候要注意檢查類層次。

主站蜘蛛池模板: 广西| 勃利县| 福州市| 佛学| 开原市| 叙永县| 介休市| 嘉兴市| 乐至县| 容城县| 马边| 太谷县| 临猗县| 四平市| 镇赉县| 芷江| 兴宁市| 资中县| 资兴市| 会昌县| 穆棱市| 广德县| 饶阳县| 沐川县| 巴林左旗| 图木舒克市| 绍兴市| 定边县| 星子县| 巍山| 陆丰市| 鹿邑县| 安新县| 桐庐县| 广宁县| 丰镇市| 云梦县| 泸水县| 舒城县| 郑州市| 鄢陵县|