[TOC]
## 隱藏
從封裝本身的意思去理解,封裝就好像是拿來一個麻袋,把小貓,小狗,小王八一起裝進麻袋,然后把麻袋封上口子。照這種邏輯看,封裝=‘隱藏’
### 如何隱藏屬性
在python中用雙下劃線開頭的方式將屬性隱藏起來(設置成私有的)
~~~
#其實這僅僅這是一種變形操作
#類中所有雙下劃線開頭的名稱如__x都會自動變形成:_類名__x的形式:
class A:
__N=0 #變形為_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是可以訪問到的,即這種操作并不是嚴格意義上的限制外部訪問,僅僅只是一種語法意義上的變形
~~~
### **自動變形的特點:**
1. 類中定義的\_\_x只能在內部使用,如self.\_\_x,引用的就是變形的結果。
2. 這種變形其實正是針對外部的變形,在外部是無法通過\_\_x這個名字訪問到的。
3. 在子類定義的\_\_x不會覆蓋在父類定義的\_\_x,
因為子類中變形成了:\_子類名\_\_x,而父類中變形成了:\_父類名\_\_x,即雙下滑線開頭的屬性在繼承給子類時,子類是無法覆蓋的。
### **變形需要注意的問題:**
1. 并沒有真正意義上限制從外部直接訪問屬性
知道了類名和屬性名就可以拼出名字:\_類名\_\_屬性,然后就可以訪問了,如a.\_A\_\_N
2. 變形的過程只在類的定義是發生一次,在定義后的賦值操作,不會變形
3. 在繼承中,父類如果不想讓子類覆蓋自己的方法,可以將方法定義為私有的
~~~
#把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
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.封裝方法
目的是隔離復雜度,舉例如下
取款是功能,而這個功能有很多功能組成:插卡、密碼認證、輸入金額、打印賬單、取錢
對使用者來說,只需要知道取款這個功能即可,其余功能我們都可以隱藏起來,這么做隔離了復雜度,同時也提升了安全性
~~~
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是一種特殊的屬性,訪問它時會執行一段功能(函數)然后返回值,可以將一個方法作為屬性對外展示.
案例:圓的周長和面積
~~~
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) #注意沒有使用c.perimeter()而是使用的c.perimeter
輸出結果:
314.1592653589793
62.83185307179586
~~~
注意:此時的特性area和perimeter不能被賦值
~~~
c.area=3 #為特性area賦值
'''
拋出異常:
AttributeError: can't set attribute
'''
~~~
### **為什么要用property**
將一個類的函數定義成特性以后,對象再去使用的時候obj.name,根本無法察覺自己的name是執行了一個函數然后計算出來的,這種特性的使用方式**遵循了統一訪問的原則**
### 面向對象的三種封裝方式
1. 【public】
這種其實就是不封裝,是對外公開的
2. 【protected】
這種封裝方式對外不公開,但對朋友(friend)或者子類公開
3. 【private】
這種封裝對誰都不公開
### pyton中的實現
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 #提供設置方法,使用過property裝飾過的函數才有此方法
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'
~~~
## 封裝與擴展性
封裝在于明確區分內外,使得類實現者可以修改封裝內的東西而不影響外部調用者的代碼;而外部使用用者只知道一個接口(函數),只要接口(函數)名、參數不變,使用者的代碼永遠無需改變。這就提供一個良好的合作基礎——或者說,只要接口這個基礎約定不變,則代碼改變不足為慮。
~~~
#類的設計者
class Room:
def __init__(self,name,owner,width,length,high):
self.name=name
self.owner=owner
self.__width=width
self.__length=length
self.__high=high
def tell_area(self): #對外提供的接口,隱藏了內部的實現細節,此時我們想求的是面積
return self.__width * self.__length
#使用者
>>> r1=Room('臥室','egon',20,20,20)
>>> r1.tell_area() #使用者調用接口tell_area
#類的設計者,輕松的擴展了功能,而類的使用者完全不需要改變自己的代碼
class Room:
def __init__(self,name,owner,width,length,high):
self.name=name
self.owner=owner
self.__width=width
self.__length=length
self.__high=high
def tell_area(self):
return self.__width * self.__length * self.__high
'''
對外提供的接口,隱藏內部實現,此時我們想求的是體積,內部邏輯變了,
只需修該一行就可以很簡答的實現,而且外部調用感知不到,
仍然使用該方法,但是功能已經變了'''
#對于仍然在使用tell_area接口的人來說,根本無需改動自己的代碼,就可以用上新功能
>>> r1.tell_area()
~~~
- 基礎部分
- 基礎知識
- 變量
- 數據類型
- 數字與布爾詳解
- 列表詳解list
- 字符串詳解str
- 元組詳解tup
- 字典詳解dict
- 集合詳解set
- 運算符
- 流程控制與循環
- 字符編碼
- 編的小程序
- 三級菜單
- 斐波那契數列
- 漢諾塔
- 文件操作
- 函數相關
- 函數基礎知識
- 函數進階知識
- lambda與map-filter-reduce
- 裝飾器知識
- 生成器和迭代器
- 琢磨的小技巧
- 通過operator函數將字符串轉換回運算符
- 目錄規范
- 異常處理
- 常用模塊
- 模塊和包相關概念
- 絕對導入&相對導入
- pip使用第三方源
- time&datetime模塊
- random隨機數模塊
- os 系統交互模塊
- sys系統模塊
- shutil復制&打包模塊
- json&pickle&shelve模塊
- xml序列化模塊
- configparser配置模塊
- hashlib哈希模塊
- subprocess命令模塊
- 日志logging模塊基礎
- 日志logging模塊進階
- 日志重復輸出問題
- re正則表達式模塊
- struct字節處理模塊
- abc抽象類與多態模塊
- requests與urllib網絡訪問模塊
- 參數控制模塊1-optparse-過時
- 參數控制模塊2-argparse
- pymysql數據庫模塊
- requests網絡請求模塊
- 面向對象
- 面向對象相關概念
- 類與對象基礎操作
- 繼承-派生和組合
- 抽象類與接口
- 多態與鴨子類型
- 封裝-隱藏與擴展性
- 綁定方法與非綁定方法
- 反射-字符串映射屬性
- 類相關內置方法
- 元類自定義及單例模式
- 面向對象的軟件開發
- 網絡-并發編程
- 網絡編程SOCKET
- socket簡介和入門
- socket代碼實例
- 粘包及粘包解決辦法
- 基于UDP協議的socket
- 文件傳輸程序實戰
- socketserver并發模塊
- 多進程multiprocessing模塊
- 進程理論知識
- 多進程與守護進程
- 鎖-信號量-事件
- 隊列與生產消費模型
- 進程池Pool
- 多線程threading模塊
- 進程理論和GIL鎖
- 死鎖與遞歸鎖
- 多線程與守護線程
- 定時器-條件-隊列
- 線程池與進程池(新方法)
- 協程與IO模型
- 協程理論知識
- gevent與greenlet模塊
- 5種網絡IO模型
- 非阻塞與多路復用IO實現
- 帶著目標學python
- Pycharm基本使用
- 爬蟲
- 案例-爬mzitu美女
- 案例-爬小說
- beautifulsoup解析模塊
- etree中的xpath解析模塊
- 反爬對抗-普通驗證碼
- 反爬對抗-session登錄
- 反爬對抗-代理池
- 爬蟲技巧-線程池
- 爬蟲對抗-圖片懶加載
- selenium瀏覽器模擬