1. 程式人生 > 實用技巧 >Python面向物件:封裝和多型

Python面向物件:封裝和多型

一、封裝

封裝是隱藏物件的屬性和實現細節,僅對外公開介面,控制在程式中屬性的讀取和修改的訪問級別。

封裝就是將抽象得到的資料和行為(或功能)相結合,形成一個有機的整體,也就是將資料與操作資料的原始碼進行有機的結合,形成“類”,其中資料和函式都是類的成員。

1、簡單理解封裝

顧名思義,封裝屬性就是把已有的屬性封裝到一個類裡面去:

class Person():

    def __init__(self, name, age, sex):
        self.name = name
        self.age = age
        self.sex = sex

jack = Person('jack', 18, '男')
#將jack、 18、 男 封裝到jack物件(self)的name、age、sex中
#name、age、sex又封裝在了Person類中
print(jack.__dict__)
#{'name': 'jack', 'age': 18, 'sex': '男'}

分析:self是一個形式引數,建立什麼物件,它就代表那個物件

2、呼叫封裝的屬性

通過物件直接呼叫:

class Person():

    def __init__(self, name, age, sex):
        self.name = name
        self.age = age
        self.sex = sex

jack = Person('jack', 18, '男')
print(jack.name, jack.sex, jack.age)
#jack 男 18

分析:在這裡,我們可以用物件名隨意呼叫到自身的屬性,並進行屬性修改,從安全的角度來看,這樣是很不安全的,所以需要將屬性隱藏起來,也就是私有化。

私有化屬性的方法:類中定義私有的,只有類內部使用,外部無法訪問(比如_(槓) __(槓槓) )

class Person():

    def __init__(self, name, age, sex):
        self.__name = name
        self.__age = age
        self.__sex = sex

jack = Person('jack', 18, '男')
print(jack._Person__name)#jack
print(jack.name)
#Error:'Person' object has no attribute 'name'

分析:
1、通過使用__(槓槓)的方法使得類Person屬性name、age、sex成功私有化,子類無法直接呼叫,但是通過jack._Person__name的方式可以呼叫到私有化的屬性,並且能對其修改,說明python在設定私有屬性的時候,只是把屬性的名字換成了其他的名字。

2、類中以_或者__的屬性,都是私有屬性,禁止外部呼叫。雖然可以通過特殊的手段獲取到,並且賦值,但是這麼做不覺的很蛋疼麼,本來就是設定私有屬性,還非要去強制修改。

私有化屬性設定好了,不可能是存在那裡誰都不讓使用的,要不然設定私有化屬性就失去了本身的意義,我們只是不想讓私有化屬性直接被隨意的修改,而不是拒絕訪問,所以還需要給私有化屬性提供查詢和修改的介面,我們只需要通過對介面的控制,就能有效的控制私有化屬性的資料安全,比如對介面進行設定,就不會出現age被賦值為負數。

class Person(object):

    def __init__(self, name, age, sex):
        self.__name = name
        self.__age = age
        self.__sex = sex

    def get_age(self):
        return self.__age

    def set_age(self, age):
        if age > 150 or age < 0:
            print('年齡必須大於0,小於150')
        else:
           self.__age = age

jack = Person('jack', 18, '男')

#訪問age屬性
print(jack.get_age())#18
#修改age屬性
jack.set_age(100)
print(jack.get_age())#100
#非法修改age屬性
jack.set_age(-20)#年齡必須大於0,小於150
print(jack.get_age())#100

分析:這樣就完美了,我們既可以訪問到例項化物件內部的屬性,也可以在資料安全的情況下(禁止非法資料修改),修改物件的屬性

3、python自帶的呼叫私有化資料的方法

前面,我們用set和get的方式來呼叫或修改物件本身的私有化屬性,達到了資料安全的目的,其實python中提供了一種直接用obj.屬性名的方式呼叫類的私有化屬性,也能保證資料安全。

class Person(object):

    def __init__(self, name, age, sex):
        self.__name = name
        self.__age = age
        self.__sex = sex

    @property
    def age(self):
        return self.__age

    @age.setter
    def age(self, age):
        if age > 150 or age < 0:
            print('年齡必須大於0,小於150')
        else:
           self.__age = age

jack = Person('jack', 18, '男')

#訪問age屬性
print(jack.age)#18
#修改age屬性
jack.age = 100
print(jack.age)#100
#非法修改age屬性
jack.age = -20#年齡必須大於0,小於150
print(jack.age)#100

分析:
1、使用 @property 裝飾器時,介面名不必與屬性名相同。

2、凡是賦值語句,就會觸發set方法。獲取屬性值,會觸發get方法。

3、我們可以使用@property裝飾器來建立只讀屬性,@property裝飾器會將方法轉換為相同名稱的只讀屬性,可以與所定義的屬性配合使用,這樣可以防止屬性被修改。

二、多型

介面的多種不同的實現方式即為多型。

多型最核心的思想就是,父類的引用可以指向子類的物件,或者介面型別的引用可以指向實現該介面的類的例項。

多型是一種執行期的行為,不是編譯期行為!在編譯期間它只知道是一個引用,只有到了執行期,引用才知道指向的是誰。這就是所謂的“軟繫結”。

多型是一項讓程式設計師“將改變的事物和未改變的事物分離開來”重要技術。

1、多型性

多型性是指指具有不同功能的函式可以使用相同的函式名,這樣就可以用一個函式名呼叫不同內容的函式。

在面向物件方法中一般是這樣表述多型性:向不同的物件傳送同一條訊息,不同的物件在接收時會產生不同的行為。

不同的行為就是指不同的實現,即執行不同的函式。

class Animals(object):

    def talk(self):
        pass

class Person(Animals):

    def talk(self):
        print('高階語言')

class Cat(Animals):

    def talk(self):
        print('喵喵喵')

class Dog(Animals):

    def talk(self):
        print('汪汪汪')


per = Person()
cat = Cat()
dog = Dog()

# 定義一個統一的介面來訪問
def fun(obj):
    obj.talk()

fun(per)#高階語言
fun(cat)#喵喵喵
fun(dog)#汪汪汪

分析:
1、per物件、cat物件、dog物件是通過Animals類實現的三種不同形態,這就是多型的體現。

2、per、cat、dog物件都是通過fun(obj)的同一種方式呼叫,實現了不同的效果,這就是多型性的體現,所以多型性可以說是一個介面,多種實現

3、多型性的優點:
3.1、增加了程式的靈活性:以不變應萬變,不論物件千變萬化,使用者都是同一種形式去呼叫,如fun(obj)
3.2、增加了程式額可擴充套件性:通過繼承Animal類派生新的類(Person類、Cat類、Dog類),使用者無需更改自己的程式碼,還是用fun(obj)去呼叫

2、鴨子型別

呼叫不同的子類將會產生不同的行為,而無須明確知道這個子類實際上是什麼,這是多型的重要應用場景。而在python中,因為鴨子型別(duck typing)使得其多型不是那麼酷,原因是python是強型別的動態指令碼語言,不使用顯示資料型別宣告,且確定一個變數的型別是在第一次給它賦值的時候。

鴨子型別是動態型別的一種風格。在這種風格中,一個物件有效的語義,不是由繼承自特定的類或實現特定的介面,而是由"當前方法和屬性的集合"決定。這個概念的名字來源於由James Whitcomb Riley提出的鴨子測試,“鴨子測試”可以這樣表述:“當看到一隻鳥走起來像鴨子、游泳起來像鴨子、叫起來也像鴨子,那麼這隻鳥就可以被稱為鴨子。”

在鴨子型別中,關注的不是物件的型別本身,而是它是如何使用的。例如,在不使用鴨子型別的語言中,我們可以編寫一個函式,它接受一個型別為"鴨子"的物件,並呼叫它的"走"和"叫"方法。在使用鴨子型別的語言中,這樣的一個函式可以接受一個任意型別的物件,並呼叫它的"走"和"叫"方法。如果這些需要被呼叫的方法不存在,那麼將引發一個執行時錯誤。任何擁有這樣的正確的"走"和"叫"方法的物件都可被函式接受的這種行為引出了以上表述,這種決定型別的方式因此得名。

鴨子型別通常得益於不測試方法和函式中引數的型別,而是依賴文件、清晰的程式碼和測試來確保正確使用。

class Duck(object):

    def walk(self):
        print('I walk like a duck')

    def swim(self):
        print('I swim like a duck')

class Person():

    def walk(self):
        print('this one walk like a duck')

    def swim(self):
        print('this man swim like a duck')


def fun(obj):
    obj.walk()
    obj.swim()

fun(Duck())
# I walk like a duck
# I swim like a duck
fun(Person())
#this one walk like a duck
#this man swim like a duck

分析:可以看出Pseron類擁有和Duck類一樣的方法,當程式呼叫Duck類,並利用了其中的walk和swim方法時,我們傳入Person類也一樣可以執行,程式並不會檢查型別是不是Duck,只要他擁有 walk()和swim()方法,就能被正確地呼叫。

再舉例,如果一個物件實現了__getitem__方法,那python的直譯器就會把它當做一個collection,就可以在這個物件上使用切片,獲取子項等方法;

如果一個物件實現了__iter__和next方法,python就會認為它是一個iterator,就可以在這個物件上通過迴圈來獲取各個子項。

class Foo:
    def __iter__(self):
        pass

    def __next__(self):
        pass

from collections import Iterable
from collections import Iterator

print(isinstance(Foo(), Iterable)) # True
print(isinstance(Foo(), Iterator)) # True

因此,這就詮釋了“當看到一隻鳥走起來像鴨子、游泳起來像鴨子、叫起來也像鴨子,那麼這隻鳥就可以被稱為鴨子。”這句話,那隻鳥是不是鴨子不重要,重要的是它有和鴨子一樣的方法,把它當鴨子呼叫,程式就不會報錯。

交流基地:630390733