第五章 面向對象編程設計與開發——續3
5.9——封裝
如何隱藏
在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(‘form A‘) defbar(self): self._foo()#只有在類內部才可以通過_foo的形式訪問到. #A._A_N是可以訪問的到的,即這種操縱並不是嚴格意義上的限制外部訪問,僅僅只是一種語法意義上的變形
這種自動變形的特點:
- 類中定義的__x只能在內部使用,如self.__x,引用的就是變形的結果。
- 這種變形其實正是針對外部的變形,在外部是無法通過__x這個名字訪問到的。
- 在子類定義的__x不會覆蓋在父類定義的__x,因為子類中變形成了:_子類名__x,而父類中變形成了:_父類名__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 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()
封裝方法的其他舉例:
- 你的身體沒有一處不體現著封裝的概念:你的身體把膀胱尿道等等這些尿的功能隱藏了起來,然後為你提供一個尿的接口就可以了(接口就是你的。。。,),你總不能把膀胱掛在身體外面,上廁所的時候就跟別人炫耀:hi,man,你瞅我的膀胱,看看我是怎麽尿的。
- 電視機本身是一個黑盒子,隱藏了所有細節,但是一定會對外提供了一堆按鈕,這些按鈕也正是接口的概念,所以說,封裝並不是單純意義的隱藏!!!
- 快門就是傻瓜相機為傻瓜們提供的方法,該方法將內部復雜的照相功能都隱藏起來了
提示:在編程語言裏,對外提供的接口(接口可理解為了一個入口),可以是函數,稱為接口函數,這與接口的概念還不一樣,接口代表一組接口函數的集合體。
特性
什麽是特性property
property是一種特殊的屬性,訪問它時會執行一段功能(函數)然後返回值
例一:BMI指數(bmi是計算而來的,但很明顯它聽起來像是一個屬性而非方法,如果我們將其做成一個屬性,更便於理解)
成人的BMI數值:
過輕:低於18.5
正常:18.5-23.9
過重:24-27
肥胖:28-32
非常肥胖, 高於32
體質指數(BMI)=體重(kg)÷身高^2(m)
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(‘egin‘,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 ‘‘‘
註意:此時的特性area和perimeter不能被賦值
.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‘
封裝與擴展性
封裝在於明確區分內外,使得類實現者可以修改封裝內的東西而不影響外部調用者的代碼;而外部使用用者只知道一個接口(函數),只要接口(函數)名、參數不變,使用者的代碼永遠無需改變。這就提供一個良好的合作基礎——或者說,只要接口這個基礎約定不變,則代碼改變不足為慮。
#類的設計者 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()
5.10——綁定方法與非綁定方法
類中定義的函數分成兩大類
一、綁定方法(綁定給誰,誰來調用就自動將它本身當作第一個參數傳入):
1.綁定到類的方法:用class method裝飾器裝飾的方法。
為類量身定制
類.boud_method(),自動將類當作第一個參數傳入
(其實對象也可以調用,但仍將類作為第一個參數傳入)
2.綁定到對象的方法:沒有被任何裝飾器裝飾的方法
為對象量身定制
對象.boud_method(),自動將對象當作第一個參數傳入
(屬於類的函數,類可以調用,但必須按照函數的規則來,沒有自動傳值那麽一說)
二、非綁定方法:用static method裝飾器裝飾的方法
1.不與類或對象綁定,類和對象都可以調用,但是沒有自動傳入那麽一說。就是一個普通工具而已
註意:與綁定到對象方法區分開,在類中直接定義的函數,沒有被任何裝飾器裝飾的,都是綁定到對象的方法,可不是普通函數,對象調用該方法會自動傳值,而static method裝飾的方法,不管誰來調用,都沒有自動傳值一說。
綁定方法
綁定給對象的方法(略)
綁定給類的方法(classmethod)
classmehtod是給類用的,即綁定到類,類在使用時會將類本身當做參數傳給類方法的第一個參數(即便是對象來調用也會將類當作第一個參數傳入),python為我們內置了函數classmethod來把類中的函數定義成類方法
settings.py HOST=‘127.0.0.1‘ PORT=3306 DB_PATH=r‘C:\Users\Administrator\PycharmProjects\test\面向對象編程\test1\db‘ #test.py import settings class MySQL: def __init__(self,host,port): self.host=host self.port=port @classmethod def from_conf(cls): print(cls) return cls(settings.HOST,settings.PORT) print(MySQL.from_conf) #<bound method MySQL.from_conf of <class ‘__main__.MySQL‘>> conn=MySQL.from_conf() conn.from_conf() #對象也可以調用,但是默認傳的第一個參數仍然是類
非綁定方法
在類內部用static method裝飾的函數即非綁定方法就是普通函數
static method不與類和對象綁定,誰都可以調用,沒有自動傳值效果
import hashlib import time class MySQL: def __init__(self,host,port): self.id=self.create_id() self.host=host self.port=port @staticmethod def create_id(): #就是一個普通工具 m=hashlib.md5(str(time.time()).encode(‘utf-8‘)) return m.hexdigest() print(MySQL.create_id) #<function MySQL.create_id at 0x0000000001E6B9D8> #查看結果為普通函數 conn=MySQL(‘127.0.0.1‘,3306) print(conn.create_id) #<function MySQL.create_id at 0x00000000026FB9D8> #查看結果為普通函數
class method與static method的對比
import settings class MySQL: def __init__(self,host,port): self.host=host self.port=port @staticmethod def from_conf(): return MySQL(settings.HOST,settings.PORT) # @classmethod #哪個類來調用,就將哪個類當做第一個參數傳入 # def from_conf(cls): # return cls(settings.HOST,settings.PORT) def __str__(self): return ‘就不告訴你‘ class Mariadb(MySQL): def __str__(self): return ‘<%s:%s>‘ %(self.host,self.port) m=Mariadb.from_conf() print(m) #我們的意圖是想觸發Mariadb.__str__,但是結果觸發了MySQL.__str__的執行,打印就不告訴你
5.11——內置方法
一、isinstance(obj,cls)和issubclass(sub,super)
isinstance(obj,cls)檢查是否obj是否是類cls的對象
class Foo(object): pass obj=Foo() isinstance(obj,Foo)
issubclass(sub,super)檢查sub類是否是super類的派生類
class Foo(object): pass class Bar(Foo): pass issubclass(Bar,Foo)
二、反射
1.什麽是反射?
反射的概念是由Smith在1982年首次提出的,主要是程序可以訪問、檢測和修改它本身狀態或行為的一種能力(自省)。這概念的提出很快引發了計算機科學領域關於應用反射性的研究。它首先被程序的設計領域所采用,並在Lisp和對象方面取得了成績。
2.python面向對象的反射;通過字符串的形式操作對象相關的屬性。python中的一切事務都是對象(都可以使用反射)
四個可以實現自省的函數 下列方法適用與類和對象(一切皆對象,類本身也是一個對象)
hasattr(object,name)
判斷object中有沒有一個name字符串對應的方法或屬性
gesattr(object,name,default=None)
def getattr(object, name, default=None): # known special case of getattr """ getattr(object, name[, default]) -> value Get a named attribute from an object; getattr(x, ‘y‘) is equivalent to x.y. When a default argument is given, it is returned when the attribute doesn‘t exist; without it, an exception is raised in that case. """ pass
setattr(x,y,v)
def setattr(x, y, v): # real signature unknown; restored from __doc__ """ Sets the named attribute on the given object to the specified value. setattr(x, ‘y‘, v) is equivalent to ``x.y = v‘‘ """ pass
delattr(x,y)
def delattr(x, y): # real signature unknown; restored from __doc__ """ Deletes the named attribute from the given object. delattr(x, ‘y‘) is equivalent to ``del x.y‘‘ """ pass
四個方法的使用演示
class BlackMedium: feature=‘Ugly‘ def __init__(self,name,addr): self.name=name self.addr=addr def sell_house(self): print(‘%s 黑中介賣房子啦,傻逼才買呢,但是誰能證明自己不傻逼‘ %self.name) def rent_house(self): print(‘%s 黑中介租房子啦,傻逼才租呢‘ %self.name) b1=BlackMedium(‘萬成置地‘,‘回龍觀天露園‘) #檢測是否含有某屬性 print(hasattr(b1,‘name‘)) print(hasattr(b1,‘sell_house‘)) #獲取屬性 n=getattr(b1,‘name‘) print(n) func=getattr(b1,‘rent_house‘) func() # getattr(b1,‘aaaaaaaa‘) #報錯 print(getattr(b1,‘aaaaaaaa‘,‘不存在啊‘)) #設置屬性 setattr(b1,‘sb‘,True) setattr(b1,‘show_name‘,lambda self:self.name+‘sb‘) print(b1.__dict__) print(b1.show_name(b1)) #刪除屬性 delattr(b1,‘addr‘) delattr(b1,‘show_name‘) delattr(b1,‘show_name111‘)#不存在,則報錯 print(b1.__dict__)
類也是對象
class Foo(object): staticField = "old boy" def __init__(self): self.name = ‘wupeiqi‘ def func(self): return ‘func‘ @staticmethod def bar(): return ‘bar‘ print getattr(Foo, ‘staticField‘) print getattr(Foo, ‘func‘) print getattr(Foo, ‘bar‘)
反射當前模塊成員
#!/usr/bin/env python # -*- coding:utf-8 -*- import sys def s1(): print ‘s1‘ def s2(): print ‘s2‘ this_module = sys.modules[__name__] hasattr(this_module, ‘s1‘) getattr(this_module, ‘s2‘)
導入其他模塊。利用反射查找該模塊是否存在某個方法
module_test.py
#!/usr/bin/env python # -*- coding:utf-8 -*- """ 程序目錄: module_test.py index.py 當前文件: index.py """ import module_test as obj #obj.test() print(hasattr(obj,‘test‘)) getattr(obj,‘test‘)()
3. 為什麽用反射之反射的好處
好處一:實現可插拔機制
有倆程序員,一個lili,一個是egon,lili在寫程序的時候需要用到egon所寫的類,但是egon去跟女朋友度蜜月去了,還沒有完成他寫的類,lili想到了反射,使用了反射機制lili可以繼續完成自己的代碼,等egon度蜜月回來後再繼續完成類的定義並且去實現lili想要的功能。
總之反射的好處就是,可以事先定義好接口,接口只有在被完成後才會真正執行,這實現了即插即用,這其實是一種‘後期綁定’,什麽意思?即你可以事先把主要的邏輯寫好(只定義接口),然後後期再去實現接口的功能
egon還沒有實現全部功能
class FtpClient: ‘ftp客戶端,但是還沒有實現具體的功能‘ def _init_(self,addr): print(‘正在連接服務器[%s]‘%addr) self.addr=addr
不影響lil的代碼編寫
#from module import FtpClient f1=FtpClient(‘192.168.1.1‘) if hasattr(f1,‘get‘): func_get=getattr(f1,‘get‘) func_get() else: print(‘---->不存在此方法‘) print(‘處理其他的邏輯‘)
好處二:動態導入模塊(基於反射當前模塊成員)
三_setattr_,_delattr_,_getattr_
三者的用法演示
class Foo: x=1 def __init__(self,y): self.y=y def __getattr__(self, item): print(‘----> from getattr:你找的屬性不存在‘) def __setattr__(self, key, value): print(‘----> from setattr‘) # self.key=value #這就無限遞歸了,你好好想想 # self.__dict__[key]=value #應該使用它 def __delattr__(self, item): print(‘----> from delattr‘) # del self.item #無限遞歸了 self.__dict__.pop(item) #__setattr__添加/修改屬性會觸發它的執行 f1=Foo(10) print(f1.__dict__) # 因為你重寫了__setattr__,凡是賦值操作都會觸發它的運行,你啥都沒寫,就是根本沒賦值,除非你直接操作屬性字典,否則永遠無法賦值 f1.z=3 print(f1.__dict__) #__delattr__刪除屬性的時候會觸發 f1.__dict__[‘a‘]=3#我們可以直接修改屬性字典,來完成添加/修改屬性的操作 del f1.a print(f1.__dict__) #__getattr__只有在使用點調用屬性且屬性不存在的時候才會觸發 f1.xxxxxx
四 二次加工標準類型(包裝)
包裝:python為大家提供了標準數據類型,以及豐富的內置方法,其實在很多場景下我們都需要基於標準數據類型來定制我們自己的數據類型,新增/改寫方法,這就用到剛學到的繼承/派生知識(其他的標準類型均可以通過下面的方式進行二次加工)
class LIst(list):#繼承list所有的屬性,也可以派生出自己新的,比如append和mid def append(self,p_object): ‘派生自己的append:加上類型檢查‘ if not isinstance(p_object,int‘): raise TupeError(‘must be int); super().append(p_object) @property def miid(self) ‘新增自己的屬性‘ index=len(self)//2 return self[index] l=List([1,2,3,4]) print(l) l.append(5) print(l) #l.append(‘1111111‘)#報錯,必須為int類型 print(l.mid) #其余的方法都繼承list的 l.inster(0,-123) print(l) l.clesr() print(l)
五 _getattribute_
回顧_getattr_
class Foo; def _init_(self,x): self.x=x def _getattr_(self,item): print(‘執行的是我’) #return self._dict_[item] f1=Foo(10) print(f1.x) f1.xxxxxx#不存在的屬性訪問;觸發_getattr_
getattribute
class Foo: def __init__(self,x): self.x=x def __getattribute__(self, item): print(‘不管是否存在,我都會執行‘) f1=Foo(10) f1.x f1.xxxxxx
兩者同時出現
#_*_coding:utf-8_*_ __author__ = ‘Linhaifeng‘ class Foo: def __init__(self,x): self.x=x def __getattr__(self, item): print(‘執行的是我‘) # return self.__dict__[item] def __getattribute__(self, item): print(‘不管是否存在,我都會執行‘) raise AttributeError(‘哈哈‘) f1=Foo(10) f1.x f1.xxxxxx #當__getattribute__與__getattr__同時存在,只會執行__getattrbute__,除非__getattribute__在執行過程中拋出異常AttributeError
六 描述符(_get_._set_,_delete_)
1 描述符是什麽:描述符本質就是一個新式類,在這個新式類中,至少實現了__get__(),__set__(),__delete__()中的一個,這也被稱為描述符協議 __get__():調用一個屬性時,觸發 __set__():為一個屬性賦值時,觸發 __delete__():采用del刪除屬性時,觸發
定義一個描述符
class Foo: #在python3中Foo是新式類,它實現了三種方法,這個類就被稱作一個描述符 def __get__(self, instance, owner): pass def __set__(self, instance, value): pass def __delete__(self, instance): pass
2 描述符是幹什麽的:描述符的作用是用來代理另外一個類的屬性的(必須把描述符定義成這個類的類屬性,不能定義到構造函數中)
引子:描述符類產生的實例進行屬性操作並不會觸發三個方法的執行
class Foo: def __get__(self, instance, owner): print(‘觸發get‘) def __set__(self, instance, value): print(‘觸發set‘) def __delete__(self, instance): print(‘觸發delete‘) #包含這三個方法的新式類稱為描述符,由這個類產生的實例進行屬性的調用/賦值/刪除,並不會觸發這三個方法 f1=Foo() f1.name=‘egon‘ f1.name del f1.name #疑問:何時,何地,會觸發這三個方法的執行
描述符
#描述符Str class Str: def __get__(self, instance, owner): print(‘Str調用‘) def __set__(self, instance, value): print(‘Str設置...‘) def __delete__(self, instance): print(‘Str刪除...‘) #描述符Int class Int: def __get__(self, instance, owner): print(‘Int調用‘) def __set__(self, instance, value): print(‘Int設置...‘) def __delete__(self, instance): print(‘Int刪除...‘) class People: name=Str() age=Int() def __init__(self,name,age): #name被Str類代理,age被Int類代理, self.name=name self.age=age #何地?:定義成另外一個類的類屬性 #何時?:且看下列演示 p1=People(‘alex‘,18) #描述符Str的使用 p1.name p1.name=‘egon‘ del p1.name #描述符Int的使用 p1.age p1.age=18 del p1.age #我們來瞅瞅到底發生了什麽 print(p1.__dict__) print(People.__dict__) #補充 print(type(p1) == People) #type(obj)其實是查看obj是由哪個類實例化來的 print(type(p1).__dict__ == People.__dict__)
第五章 面向對象編程設計與開發——續3