封裝,特性,多型

北京-IT拾荒者發表於2019-02-19

封裝
從封裝本身的意思去理解,封裝就好像是拿來一個麻袋,把小貓,小狗,小王八一起裝進麻袋,然後把麻袋封上口子。照這種邏輯看,封裝=‘隱藏’,這種理解是相當片面的

先看如何隱藏
在python中用雙下劃線開頭的方式將屬性隱藏起來(設定成私有的)

#其實這僅僅這是一種變形操作且僅僅只在類定義階段發生變形
#類中所有雙下劃線開頭的名稱如__x都會在類定義時自動變形成:_類名__x的形式:
class A:
   __N=0 #類的資料屬性就應該是共享的,但是語法上是可以把類的資料屬性設定成私有的如__N,會變形為_A__N
   def __init__(self):
       self.__X=10 #變形為self._A__X
   def __foo(self): #變形為_A__foo
       print(`from A`)
   def bar(self):
       self.__foo() #只有在類內部才可以通過__foo的形式訪問到.

#A._A__N是可以訪問到的,
#這種,在外部是無法通過__x這個名字訪問到。
複製程式碼

這種變形需要注意的問題是:

1.這種機制也並沒有真正意義上限制我們從外部直接訪問屬性,知道了類名和屬性名就可以拼出名字:_類名__屬性,然後就可以訪問了,如a._A__N,即這種操作並不是嚴格意義上的限制外部訪問,僅僅只是一種語法意義上的變形,主要用來限制外部的直接訪問。

2.變形的過程只在類的定義時發生一次,在定義後的賦值操作,不會變形

封裝,特性,多型

3.在繼承中,父類如果不想讓子類覆蓋自己的方法,可以將方法定義為私有的

#正常情況
>>> class A:
...    def fa(self):
...        print(`from A`)
...    def test(self):
...        self.fa()
... 
>>> class B(A):
...    def fa(self):
...        print(`from B`)
... 
>>> b=B()
>>> b.test()
from B

#把fa定義成私有的,即__fa
>>> class A:
...    def __fa(self): #在定義時就變形為_A__fa
...        print(`from A`)
...    def test(self):
...        self.__fa() #只會與自己所在的類為準,即呼叫_A__fa
... 
>>> class B(A):
...    def __fa(self):
...        print(`from B`)
... 
>>> b=B()
>>> b.test()
from A
複製程式碼

封裝不是單純意義的隱藏
封裝的真諦在於明確地區分內外,封裝的屬性可以直接在內部使用,而不能被外部直接使用,然而定義屬性的目的終歸是要用,外部要想用類隱藏的屬性,需要我們為其開闢介面,讓外部能夠間接地用到我們隱藏起來的屬性,那這麼做的意義何在???

1:封裝資料:將資料隱藏起來這不是目的。隱藏起來然後對外提供操作該資料的介面,然後我們可以在介面附加上對該資料操作的限制,以此完成對資料屬性操作的嚴格控制。

class Teacher:
   def __init__(self,name,age):
       # self.__name=name
       # self.__age=age
       self.set_info(name,age)

   def tell_info(self):
       print(`姓名:%s,年齡:%s` %(self.__name,self.__age))
   def set_info(self,name,age):
       if not isinstance(name,str):
           raise TypeError(`姓名必須是字串型別`)
       if not isinstance(age,int):
           raise TypeError(`年齡必須是整型`)
       self.__name=name
       self.__age=age

t=Teacher(`egon`,18)
t.tell_info()

t.set_info(`egon`,19)
t.tell_info()
複製程式碼

2:封裝方法:目的是隔離複雜度

封裝方法舉例:

2.1 你的身體沒有一處不體現著封裝的概念:你的身體把膀胱尿道等等這些尿的功能隱藏了起來,然後為你提供一個尿的介面就可以了(介面就是你的。。。,),你總不能把膀胱掛在身體外面,上廁所的時候就跟別人炫耀:hi,man,你瞅我的膀胱,看看我是怎麼尿的。

2.2 電視機本身是一個黑盒子,隱藏了所有細節,但是一定會對外提供了一堆按鈕,這些按鈕也正是介面的概念,所以說,封裝並不是單純意義的隱藏!!!

2.3. 快門就是傻瓜相機為傻瓜們提供的方法,該方法將內部複雜的照相功能都隱藏起來了

提示:在程式語言裡,對外提供的介面(介面可理解為了一個入口),可以是函式,稱為介面函式,這與介面的概念還不一樣,介面代表一組介面函式的集合體。

#取款是功能,而這個功能有很多功能組成:插卡、密碼認證、輸入金額、列印賬單、取錢
#對使用者來說,只需要知道取款這個功能即可,其餘功能我們都可以隱藏起來,很明顯這麼做
#隔離了複雜度,同時也提升了安全性

class ATM:
   def __card(self):
       print(`插卡`)
   def __auth(self):
       print(`使用者認證`)
   def __input(self):
       print(`輸入取款金額`)
   def __print_bill(self):
       print(`列印賬單`)
   def __take_money(self):
       print(`取款`)

   def withdraw(self):
       self.__card()
       self.__auth()
       self.__input()
       self.__print_bill()
       self.__take_money()
a=ATM()
a.withdraw()
複製程式碼

特性(property)
什麼是特性property

property是一種特殊的屬性,訪問它時會執行一段功能(函式)然後返回值

例一:BMI指數(bmi是計算而來的,但很明顯它聽起來像是一個屬性而非方法,如果我們將其做成一個屬性,更便於理解)

成人的BMI數值:
過輕:低於18.5
正常:18.5-23.9
過重:24-27
肥胖:28-32
非常肥胖, 高於32
  體質指數(BMI)=體重(kg)÷身高^2(m)
  EX:70kg÷(1.75×1.75)=22.86


class People:
   def __init__(self,name,weight,height):
       self.name=name
       self.weight=weight
       self.height=height
   @property
   def bmi(self):
       return self.weight / (self.height**2)
p1=People(`egon`,75,1.85)
print(p1.bmi)
複製程式碼

例二:圓的周長和麵積

import math
class Circle:
   def __init__(self,radius): #圓的半徑radius
       self.radius=radius

   @property
   def area(self):
       return math.pi * self.radius**2 #計算面積

   @property
   def perimeter(self):
       return 2*math.pi*self.radius #計算周長

c=Circle(10)
print(c.radius)
print(c.area) #可以向訪問資料屬性一樣去訪問area,會觸發一個函式的執行,動態計算出一個值
print(c.perimeter) #同上
```
輸出結果:
314.1592653589793
62.83185307179586
```


#注意:此時的特性arear和perimeter不能被賦值c.area=3 #為特性area賦值```丟擲異常: AttributeError: can`t set attribute```
複製程式碼

為什麼要用property

將一個類的函式定義成特性以後,物件再去使用的時候obj.name,根本無法察覺自己的name是執行了一個函式然後計算出來的,這種特性的使用方式遵循了統一訪問的原則

除此之外,看下

ps:物件導向的封裝有三種方式:

【public】
這種其實就是不封裝,是對外公開的
【protected】
這種封裝方式對外不公開,但對朋友(friend)或者子類(形象的說法是“兒子”,但我不知道為什麼大家 不說“女兒”,就像“parent”本來是“父母”的意思,但中文都是叫“父類”)公開
【private】
這種封裝對誰都不公開
複製程式碼

python並沒有在語法上把它們三個內建到自己的class機制中,在C++裡一般會將所有的所有的資料都設定為私有的,然後提供set和get方法(介面)去設定和獲取,在python中通過property方法可以實現

class Foo:
   def __init__(self,val):
       self.__NAME=val #將所有的資料屬性都隱藏起來

   @property
   def name(self):
       return self.__NAME #obj.name訪問的是self.__NAME(這也是真實值的存放位置)

   @name.setter
   def name(self,value):
       if not isinstance(value,str):  #在設定值之前進行型別檢查
           raise TypeError(`%s must be str` %value)
       self.__NAME=value #通過型別檢查後,將值value存放到真實的位置self.__NAME

   @name.deleter
   def name(self):
       raise TypeError(`Can not delete`)

f=Foo(`egon`)
print(f.name)
# f.name=10 #丟擲異常`TypeError: 10 must be str`
del f.name #丟擲異常`TypeError: Can not delete`
複製程式碼

多型
多型指的是一類事物有多種形態

動物有多種形態:人,狗,豬

import abc
class Animal(metaclass=abc.ABCMeta): #同一類事物:動物
   @abc.abstractmethod
   def talk(self):
       pass

class People(Animal): #動物的形態之一:人
   def talk(self):
       print(`say hello`)

class Dog(Animal): #動物的形態之二:狗
   def talk(self):
       print(`say wangwang`)

class Pig(Animal): #動物的形態之三:豬
   def talk(self):
       print(`say aoao`)
複製程式碼

檔案有多種形態:文字檔案,可執行檔案

import abc
class File(metaclass=abc.ABCMeta): #同一類事物:檔案
   @abc.abstractmethod
   def click(self):
       pass

class Text(File): #檔案的形態之一:文字檔案
   def click(self):
       print(`open file`)

class ExeFile(File): #檔案的形態之二:可執行檔案
   def click(self):
       print(`execute file`)
複製程式碼

多型性
什麼是多型動態繫結(在繼承的背景下使用時,有時也稱為多型性)多型性是指在不考慮例項型別的情況下使用例項

在物件導向方法中一般是這樣表述多型性:向不同的物件傳送同一條訊息(!!!obj.func():是呼叫了obj的方法func,又稱為向obj傳送了一條訊息func),不同的物件在接收時會產生不同的行為(即方法)。也就是說,每個物件可以用自己的方式去響應共同的訊息。所謂訊息,就是呼叫函式,不同的行為就是指不同的實現,即執行不同的函式。 比如:老師.下課鈴響了(),學生.下課鈴響了(),老師執行的是下班操作,學生執行的是放學操作,雖然二者訊息一樣,但是執行的效果不同

多型性分為靜態多型性和動態多型性

靜態多型性:如任何型別都可以用運算子+進行運算

  動態多型性:如下

peo=People()
dog=Dog()
pig=Pig()

#peo、dog、pig都是動物,只要是動物肯定有talk方法
#於是我們可以不用考慮它們三者的具體是什麼型別,而直接使用
peo.talk()
dog.talk()
pig.talk()

#更進一步,我們可以定義一個統一的介面來使用
def func(obj):
   obj.talk()
複製程式碼

為什麼要用多型性(多型性的好處)

其實大家從上面多型性的例子可以看出,我們並沒有增加什麼新的知識,也就是說python本身就是支援多型性的,這麼做的好處是什麼呢?

1.增加了程式的靈活性

  以不變應萬變,不論物件千變萬化,使用者都是同一種形式去呼叫,如func(animal)

2.增加了程式額可擴充套件性

  通過繼承animal類建立了一個新的類,使用者無需更改自己的程式碼,還是用func(animal)去呼叫  

class Cat(Animal): #屬於動物的另外一種形態:貓
   def talk(self):
       print(`say miao`)

def func(animal): #對於使用者來說,自己的程式碼根本無需改動
   animal.talk()

cat1=Cat() #例項出一隻貓
func(cat1) #甚至連呼叫方式也無需改變,就能呼叫貓的talk功能
say miao

```
這樣我們新增了一個形態Cat,由Cat類產生的例項cat1,使用者可以在完全不需要修改自己程式碼的情況下。使用和人、狗、豬一樣的方式呼叫cat1的talk方法,即func(cat1)
```
複製程式碼

鴨子型別

逗比時刻:

  Python崇尚鴨子型別,即‘如果看起來像、叫聲像而且走起路來像鴨子,那麼它就是鴨子,python程式設計師通常根據這種行為來編寫程式。例如,如果想編寫現有物件的自定義版本,可以繼承該物件

也可以建立一個外觀和行為像,但與它無任何關係的全新物件,後者通常用於儲存程式元件的鬆耦合度。

例1:利用標準庫中定義的各種‘與檔案類似’的物件,儘管這些物件的工作方式像檔案,但他們沒有繼承內建檔案物件的方法

#二者都像鴨子,二者看起來都像檔案,因而就可以當檔案一樣去用
class TxtFile:
   def read(self):
       pass

   def write(self):
       pass

class DiskFile:
   def read(self):
       pass
   def write(self):
       pass
複製程式碼

例2:其實大家一直在享受著多型性帶來的好處,比如Python的序列型別有多種形態:字串,列表,元組,多型性體現如下

#str,list,tuple都是序列型別
s=str(`hello`)
l=list([1,2,3])
t=tuple((4,5,6))

#我們可以在不考慮三者型別的前提下使用s,l,t
s.__len__()
l.__len__()
t.__len__()
len(s)
len(l)
len(t)
複製程式碼
封裝,特性,多型

識別圖中二維碼,歡迎關注python寶典

相關文章