第8天,面向對象進階
@(python)[筆記]
目錄
一、isinstance()和issubclass()
1. isinstance()
2. issubclass()
二、反射
1. hasattr()
2. getattr()
3. setattr()
4. delattr()
5. 擴展用法
三、__setattr__、__delattr__、__getattr__
四、二次加工標準類型(包裝)
授權
五、__next__和__iter__實現(xiàn)迭代器協(xié)議
六、__doc__
七、__module__和__class__
八、__str__
九、__del__析構方法
十、__setitem__,__getitem__,__delitem__
十一、__enter__和__exit__
十二、__call__
十三、元類metaclass
先來看看exec
未完
一、isinstance()和issubclass()
1. isinstance()
語法:isinstance(obj,cls)
功能:檢查對象obj是否是類cls的實例
class foo:
pass
obj = foo()
print(isinstance(obj,foo)) #True
2. issubclass()
語法:issubclass(sub, super)
功能:檢查sub類是否是super類的派生類(子類)
class foo:
pass
obj = foo()
print(isinstance(obj,foo)) #True
class bar(foo):
pass
print(issubclass(bar,foo)) #True
二、反射
反射主要是指程序可以訪問、檢測和修改它本身狀態(tài)或行為的一種能力,也可以稱為自省。
python面向對象中的反射是指通過字符串的形式操作對象相關的屬性。python中的一切事物都是對象,都可以使用反射。
python中可以實現(xiàn)自省的四個函數(shù):
下列方法適用于類和對象(一切皆對象,類本身也是一個對象)
1. hasattr()
語法:hasattr(object,"name")
功能:判斷object中有沒有一個name字符串對應的方法或屬性。
用法:
class foo:
name = "egon"
pass
obj = foo()
print(hasattr(obj,"name")) #True
print(hasattr(obj,"func")) #True
print(hasattr(foo,"name")) #True
print(hasattr(foo,"func")) #True
2. getattr()
語法:getattr(object, "name"[, default=None])
功能:從對象object獲取一個name字符串對應的方法,如果沒有name字符串對應的方法,并且沒有設置default,則報錯;如果沒有name字符串對應的方法,設置了default,則將default的值返回。getattr(object,"name")等價于object.name
class foo:
name = "egon"
def func(self):
print("Hello world")
obj = foo()
print(getattr(obj,"func"))
#<bound method foo.func of <__main__.foo object at 0x000000000112ACC0>>
#可以看出,輸出結果是一個綁定方法
f1 = getattr(obj,"func")
f2 = getattr(obj,"bar","不存在")
f1() #Hello world
print(f2) #不存在
3. setattr()
語法:setattr(x, y, v)
功能:設置屬性,setattr(x,'y',v)等價于“x.y = v”
用法:
class foo:
name = "egon"
def func(self):
print("Hello world")
obj = foo()
setattr(obj,"age",20)
setattr(obj,"show_name",lambda self:self.name+"_NB")
print(obj.__dict__) #查看屬性字典
print(obj.show_name(obj))
'''
輸出:
{'age': 20, 'show_name': <function <lambda> at 0x000000000115E2F0>}
egon_NB
'''
4. delattr()
語法:delattr(x, y)
功能:刪除屬性,delattr(x,'y')相當于```del x.y`''
用法:
delattr(obj,"show_name")
# delattr(obj,"sex") #不存在,則報錯
print(obj.__dict__) #{'age': 20}
5. 擴展用法
反射當前模塊
import sys
def s1():
print("s1")
def s2():
print("s2")
this_module = sys.modules[__name__]
print(hasattr(this_module,"s1")) #True
print(getattr(this_module,"s2"))
#<function s2 at 0x00000000006EE378>,加括號可執(zhí)行
導入其他模塊,利用反射查找該模塊是否存在某個方法
程序目錄:
- module_test.py
- current.py
# module_test.py
def test():
print("from the module_test.test")
#current.py
import module_test as mt
print(hasattr(mt,"test")) #True
f = getattr(mt,"test","不存在")
f() #from the module_test.test
三、__setattr__、__delattr__、__getattr__
__setattr__ :添加/修改屬性會觸發(fā)它的執(zhí)行;
__delattr__ :刪除屬性的時候會觸發(fā);
__getattr__ :只有在使用點調(diào)用屬性且屬性不存在的時候才會觸發(fā)。
class Foo:
x = 1
def __init__(self,y):
self.y = y
def __getattr__(self, item):
print("---> 你找的屬性不存在")
def __setattr__(self, key, value):
print("---> from __setattr__")
# self.key = value #這樣會陷入無限遞歸,只能通過__dict__字典進行賦值
self.__dict__[key] = value #這樣才可以正確賦值
def __delattr__(self, item):
print("---> from __delattr__")
f1 = Foo(10) #相當于設值,觸發(fā)__setattr__執(zhí)行,---> from __setattr__
print(f1.__dict__)
#{},直接打印為空,是因為你自己重寫了__setattr__方法,
# 而你在__setattr__方法中沒有真正賦值
f1.z #觸發(fā)__getattr__執(zhí)行,---> 你找的屬性不存在
del f1.x #觸發(fā)__delattr__執(zhí)行,---> from __delattr__
四、二次加工標準類型(包裝)
包裝:python為用戶提供了標準數(shù)據(jù)類型,以及豐富的內(nèi)置方法,其在很多場景下,我們需要基于標準數(shù)據(jù)類型來定制我們自己的數(shù)據(jù)類型,新增 / 改寫方法,這就用到繼承和派生的知識,其他標準類型均可以通過下面的方式進行二次加工。
示例1:對list進行二次加工,限制append只能增加int整型數(shù)據(jù);并且增加mid方法,得到列表的中間值;其余方法都繼承list的。
class List(list):
def append(self,p_object):
#派生出自己的append方法,會覆蓋父類list中的append方法
if not isinstance(p_object,int):
raise TypeError("%s must be int"%p_object)
super(List, self).append(p_object)
@property
#中間值聽起來更像一個屬性,而非方法,所以使用property
def mid(self):
mid_num = len(self) // 2
return self[mid_num]
l = List([1,2,3,4])
print(l) #[1, 2, 3, 4]
l.append(5)
print(l) #[1, 2, 3, 4, 5]
print(l.mid) #中間值3
示例二:為list的clear方法增加權限
class List(list):
def __init__(self,item,perm=False):
super(List, self).__init__(item)
self.perm = perm
#先設定一個默認的權限
def clear(self):
if not self.perm:
raise PermissionError("權限拒絕")
super(List, self).clear()
l = List([1,2,3])
print(l) #[1, 2, 3]
# l.clear() #拋出“權限拒絕”的異常
l = List([1,2,3],True) #給一個授權參數(shù)為True
l.clear()
print(l) #[],可以正常清空列表
授權
授權是包裝的一個特性,包裝一個類型通常是對已存在的類型的一些定制,這種做法可以新建、修改或刪除原有產(chǎn)品的功能,其它的則保持原樣。授權的過程就是所有更新的功能都是由新類的某部分來處理,但已存在的功能就授權給對象的默認屬性。
實現(xiàn)授權的關鍵點就是覆蓋__getattr__方法
示例三:利用open()函數(shù)重新定制一個文件處理器,增加寫內(nèi)容添加時間的功能;
import time
class FileHandler:
def __init__(self,filename,mode='r',encoding="utf-8"):
self.file = open(filename,mode,encoding=encoding)
#self.file獲取到一個文件句柄
def write(self,line):
t = time.strftime("%Y-%m-%d %X")
self.file.write("%s %s"%(t,line))
def __getattr__(self, item):
return getattr(self.file,item)
#當對象調(diào)用FileHandler類不存在的方法時,會返回open()函數(shù)的item字符串對應的方法;
f1 = FileHandler("a.txt","r+")
f1.write("你好嗎\n")
f1.seek(0)
print(f1.tell()) #0
示例四:
#我們來加上b模式支持
import time
class FileHandle:
def __init__(self,filename,mode='r',encoding='utf-8'):
if 'b' in mode:
self.file=open(filename,mode)
else:
self.file=open(filename,mode,encoding=encoding)
self.filename=filename
self.mode=mode
self.encoding=encoding
def write(self,line):
if 'b' in self.mode:
if not isinstance(line,bytes):
raise TypeError('must be bytes')
self.file.write(line)
def __getattr__(self, item):
return getattr(self.file,item)
def __str__(self):
if 'b' in self.mode:
res="<_io.BufferedReader name='%s'>" %self.filename
else:
res="<_io.TextIOWrapper name='%s' mode='%s' encoding='%s'>" %(self.filename,self.mode,self.encoding)
return res
f1=FileHandle('b.txt','wb')
# f1.write('你好啊啊啊啊啊') #自定制的write,不用在進行encode轉成二進制去寫了,簡單,大氣
f1.write('你好啊'.encode('utf-8'))
print(f1)
f1.close()
示例五:利用授權的方式重新定制list的append方法,只能往列表中添加int整型數(shù)據(jù);與示例一作對比,看一下兩者的區(qū)別。
#授權,定制list
class List:
def __init__(self,seq):
self.seq = list(seq)
def append(self,p_object):
if not isinstance(p_object,int):
raise TypeError("'%s' must be int"%p_object)
self.seq.append(p_object)
def __getattr__(self, item):
return getattr(self.seq,item)
def __str__(self):
return str(self.seq)
l = List([1,2,3])
l.append("4") #TypeError: '4' must be int
print(l)
總結:授權這種方式用在定制源不是類的情況下。例如示例三中,定制
open()函數(shù)
五、__next__和__iter__實現(xiàn)迭代器協(xié)議
迭代器必須要有__next__和__iter__方法;
現(xiàn)在就來自己實現(xiàn)一個迭代器吧;
簡單實現(xiàn):
class Foo:
def __init__(self,x):
self.x=x
def __iter__(self):
return self
def __next__(self):
n=self.x
self.x+=1
return self.x
f=Foo(3)
for i in f:
print(i)
模擬實現(xiàn)range()函數(shù):
class foo:
def __init__(self,start,stop=None,step=1):
self.start = start
self.stop = stop
self.step = step
if isinstance(self.start,str) \
or isinstance(self.stop,str) \
or isinstance(self.step,str):
raise InterruptedError("Must be Numeric")
def __next__(self):
if self.stop:
res = self.compute()
else:
self.stop = self.start
# 如果只傳了一個數(shù)字,則將其設為迭代停止數(shù)字
self.start = 0
# 如果只傳了一個數(shù)字,則默認從0開始迭代
res = self.compute()
return res
def __iter__(self):
return self
#迭代器執(zhí)行__iter__方法,返回的是它本身
def compute(self):
if self.start >= self.stop:
#判斷是否超出迭代停止數(shù)字
raise StopIteration
# 這是超出迭代器范圍后迭代器協(xié)議規(guī)定的拋出異常
iter_val = self.start #迭代后的值
self.start += self.step
return iter_val
for i in foo('a',20,3.5):
print(i)
for i in foo(5,15,3):
print(i)
六、__doc__
查看對象的描述信息
def func():
'''我是函數(shù)的描述信息'''
pass
print(func.__doc__)
class Foo:
'''我是類的描述信息'''
pass
class bar(Foo):
pass
obj = Foo()
print(obj.__doc__)
print(Foo.__doc__)
print(bar.__doc__) #該屬性不會繼承給子類
'''
輸出:
我是函數(shù)的描述信息
我是類的描述信息
我是類的描述信息
None
'''
七、__module__和__class__
__module__ 表示當前操作的對象在那個模塊
__class__ 表示當前操作的對象的類是什么
以下兩個文件在同一級目錄下:
#current.py
class C:
def __init__(self):
self.name = "alex"
#test.py
import current
def test():
print("from the test.test")
obj = current.C()
print(obj.name)
print(obj.__class__) #輸出是哪個類實例化得到的對象
print(obj.__module__) #輸出屬性哪個模塊
'''
輸出:
alex
<class 'current.C'>
current
'''
八、__str__
l = list([1,2,3])
print(l) #打印的是[1, 2, 3]
class mysql:
def __init__(self,host,port):
self.host = host
self.port = port
conn = mysql("127.0.0.1","3306")
print(conn) #打印的是<__main__.mysql object at 0x0000000000701898>
從以上兩段代碼可以看到,我們自己定義的類,生成的對象直接被打印時,打印的是對象的內(nèi)存地址,而這并不是我們想要的,我們實際想要的也是像第一段代碼那樣返回一個有用的信息,這時就要用到__str__這個內(nèi)置方法了,它定義在類的內(nèi)部,只要類被實例化,就會自動觸發(fā)__str__的執(zhí)行,并返回一個值給實例化后的對象。如下示例:
class mysql:
def __init__(self,host,port):
self.host = host
self.port = port
def __str__(self):
return "Host:%s,Port:%s"%(self.host,self.port)
conn = mysql("127.0.0.1","3306")
#會將`__str__`方法的返回值賦值給對象conn
print(conn)
'''
輸出:
Host:127.0.0.1,Port:3306
'''
九、__del__析構方法
析構方法,當對象在內(nèi)存中被釋放時,會自動觸發(fā)__del__執(zhí)行。
注:此方法一般無須定義,因為Python是一門高級語言,程序員在使用時無需關心內(nèi)存的分配和釋放,因為此工作都是交給Python解釋器來執(zhí)行,所以,析構函數(shù)的調(diào)用是由解釋器在進行垃圾回收時自動觸發(fā)執(zhí)行的。
簡單示例:
class Foo:
def __del__(self):
print("執(zhí)行__del__")
f1 = Foo()
del f1 #釋放f1,會觸發(fā)__del__執(zhí)行
print("------->")
'''
輸出:
執(zhí)行__del__
------->
'''
再看看下面這種情況:
class Foo:
def __del__(self):
print("執(zhí)行__del__")
f1 = Foo()
# del f1 #注釋掉
print("------->")
'''
輸出:
------->
執(zhí)行__del__
'''
#可以看出__del__仍然會在程序執(zhí)行完畢后,被觸發(fā)執(zhí)行
十、__setitem__,__getitem__,__delitem__
類中有這三個方法,那就意味著類中的屬性可以像字典一樣進行操作;
class Foo:
def __init__(self,name):
self.name = name
def __getitem__(self, item):
print("from __getitem__")
def __setitem__(self, key, value):
print("from __setitem__")
self.__dict__[key] = value #加入此代碼,才會真正設值
def __delitem__(self, key):
print("from __delitem__")
self.__dict__.pop(key)
f1 = Foo("egon")
# f1.age = 18 #不會觸發(fā)__setitem__的執(zhí)行
f1["age_2"] = 20 #會觸發(fā)__setitem__的執(zhí)行,并不會真正設值;
print(f1.__dict__)
del f1["age_2"]
print(f1.__dict__)
print(f1["name"]) #會觸發(fā)__getitem__執(zhí)行,如果__getitem__沒有設定返回值,則會返回一個None
'''
輸出結果:
from __setitem__
{'name': 'egon', 'age_2': 20}
from __delitem__
{'name': 'egon'}
from __getitem__
None
'''
#從結果可以看出,age_2 = 20并沒有真正設值成功
十一、__enter__和__exit__
通過__enter__和__exit__這兩個方法可以實現(xiàn)上下文件管理協(xié)議,即with語句。為了讓一個對象兼容with語句,必須在這個對象的類中聲明__enter__和__exit__方法。
class Open:
def __init__(self,name):
self.name = name
def __enter__(self):
#出現(xiàn)with語句,對象的__enter__方法就會被觸發(fā),有返回值則賦值給as聲明的變量
print("from __enter__")
return 3 #會返回給as語句后面的變量
def __exit__(self, exc_type, exc_val, exc_tb):
print("from __exit__, with代碼塊執(zhí)行完畢時觸發(fā)")
with Open("egon") as f:
print("from with 代碼塊")
print("f:",f)
'''
輸出:
from __enter__
from with 代碼塊
f: 3
from __exit__, with代碼塊執(zhí)行完畢時觸發(fā)
'''
__exit__(self, exc_type, exc_val, exc_tb)中的三個參數(shù)的含義:
-
exc_type代表異常類型 -
exc_val代表異常值 -
exc_tb代表異常的追溯信息
class Open:
def __init__(self,name):
self.name = name
def __enter__(self):
#出現(xiàn)with語句,對象的__enter__方法就會被觸發(fā),有返回值則賦值給as聲明的變量
# print("from __enter__")
return self #會返回給as語句后面的變量
def __exit__(self, exc_type, exc_val, exc_tb):
print("from __exit__, with代碼塊執(zhí)行完畢時觸發(fā)")
print("exc_type:",exc_type)
print("exc_val:",exc_val)
print("exc_tb:",exc_tb)
return True #返回True,則表示不拋出異常,with代碼之后的代碼可正常執(zhí)行,但with子代碼raise后的代碼不會執(zhí)行。
with Open("egon") as f:
print("from with 代碼塊")
raise AttributeError("屬性錯誤")
# print("=====> 在異常之后") #不會執(zhí)行
print("=====> 在異常之后,with之外") #__exit__返回True才會執(zhí)行
'''
輸出:
from with 代碼塊
from __exit__, with代碼塊執(zhí)行完畢時觸發(fā)
exc_type: <class 'AttributeError'>
exc_val: 屬性錯誤
exc_tb: <traceback object at 0x014B58A0>
=====> 在異常之后
'''
示例:模擬open(),并實現(xiàn)上下文管理
#模擬open()功能
class Open:
def __init__(self,filename,mode='r',encoding="utf-8"):
self.filename = filename
self.mode = mode
self.encoding = encoding
def __enter__(self):
self.file = open(self.filename,self.mode,encoding =self.encoding)
return self.file
def __exit__(self, exc_type, exc_val, exc_tb):
self.file.close()
return True #遇到異常不拋出,with之外的代碼繼續(xù)執(zhí)行
with Open("a.txt","w+") as f:
f.write("11111\n")
f.write("22222\n")
f.seek(0)
print(f.tell())
f.abc #拋出異常,交給__exit__處理
十二、__call__
對象后面加括號,就會觸發(fā)__call__方法的執(zhí)行;反之類中有了__call__方法,通過這個類生成的對象,才能加括號執(zhí)行。
class foo:
def __init__(self):
print("from __init__")
def __call__(self, *args, **kwargs):
print("from __call__")
obj = foo() #執(zhí)行__init__
obj() #執(zhí)行__call__
'''
輸出:
from __init__
from __call__
'''