一、反射
1 什么是反射
反射的概念是由Smith在1982年首次提出的,主要是指程序可以訪問、檢測和修改它本身狀態或行為的一種能力(自省)。這一概念的提出很快引發了計算機科學領域關于應用反射性的研究。它首先被程序語言的設計領域所采用,并在Lisp和面向對象方面取得了成績。
?
2 python面向對象中的反射:通過字符串的形式操作對象相關的屬性。python中的一切事物都是對象(都可以使用反射)
四個可以實現自省的函數
下列方法適用于類和對象(一切皆對象,類本身也是一個對象)
導入其他模塊,利用反射查找該模塊是否存在某個方法
四種方法使用效果展示:
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__
)
3 為什么用反射之反射的好處
有倆程序員,一個simon,一個是zhurui,simon在寫程序的時候需要用到zhurui所寫的類,但是zhurui去跟女朋友度蜜月去了,還沒有完成他寫的類,simon想到了反射,使用了反射機制simon可以繼續完成自己的代碼,等zhurui度蜜月回來后再繼續完成類的定義并且去實現simon想要的功能。
總之反射的好處就是,可以事先定義好接口,接口只有在被完成后才會真正執行,這實現了即插即用,這其實是一種‘后期綁定’,什么意思?即你可以事先把主要的邏輯寫好(只定義接口),然后后期再去實現接口的功能
class
FtpClient:
'
ftp客戶端,但是還么有實現具體的功能
'
def
__init__
(self,addr):
print
(
'
正在連接服務器[%s]
'
%
addr)
self.addr
=addr
#
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__
三者用法展示:
#
#getattr
#
class Foo:
#
x=1
#
def __init__(self,y):
#
self.y=y
#
#
def __getattr__(self, item):
#
print('執行__getattr__')
#
#
f1=Foo(10)
#
print(f1.y)
#
print(getattr(f1,'y')) #len(str) ------>str.__len__()
##delattr
#
#
class Foo:
#
x=1
#
def __init__(self,y):
#
self.y=y
#
#
def __delattr__(self, item):
#
print('刪除操作__delattr__')
#
#
f1=Foo(10)
#
del f1.y
#
print(f1.x)
#
#setattr 添加/修改屬性會觸發
class
Foo:
x
=1
def
__init__
(self,y):
self.y
=
y
def
__setattr__
(self,key,value):
print
(
'
__setattr__執行
'
)
#
self.key=value
self.
__dict__
[key]=
value
f1
=Foo(10
)
print
(f1.
__dict__
)
f1.z
=2
print
(f1.
__dict__
)
三、包裝標準類型
包裝:python為大家提供了標準數據類型,以及豐富的內置方法,其實在很多場景下我們都需要基于標準數據類型來定制我們自己的數據類型,新增/改寫方法,這就用到了我們剛學的繼承/派生知識 其他的標準類型均可以通過下面的方式進行二次加工
class
List(list):
def
append(self,p_object):
if
type(p_object)
is
str:
#
self.append(p_object)
super().append(p_object)
else
:
print
(
'
只能添加字符串類型
'
)
def
show_middle(self):
mid_index
=int(len(self)/2
)
return
self[mid_index]
#
l2=List('hello world')
#
print(l2,type(l2))
l1
=List(
'
helloworld
'
)
#
print(l1,type(l1))
l1.append(123456
)
l1.append(
'
sb
'
)
print
(l1)
?
四、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:
pass
class
Bar(Foo):
pass
f1
=
Foo()
print
(isinstance(f1,Foo))
print
(issubclass(Bar,Foo))
五、__getattribute__
class
Foo:
def
__init__
(self,x):
self.x
=
x
def
__getattr__
(self, item):
print
(
'
執行的是getattr
'
)
def
__getattribute__
(self, item):
print
(
'
執行的是getattribute
'
)
raise
AttributeError(
'
拋出異常了
'
)
f1
=Foo(11
)
#
f1.x
f1.xxxxxx
#
不存在的屬性訪問,觸發__getattr__
六、__setitem__,__getitem__,__delitem__
class
Foo:
def
__getitem__
(self, item):
print
(
'
getitem
'
)
#
retun self.__dict__
def
__setitem__
(self, key, value):
print
(
'
setitem
'
)
self.
__dict__
[key]=
value
def
__delitem__
(self, key):
print
(
'
delitem
'
)
self.
__dict__
.pop(key)
f1
=
Foo()
print
(f1.
__dict__
)
#
f1.name='simon'
f1[
'
name
'
]=
'
simon
'
f1[
'
age
'
]=28
print
(
'
===========>
'
,f1.
__dict__
)
#
del f1.name
#
print(f1.__dict__)
#
print(f1.age)
del
f1[
'
name
'
]
print
(f1.
__dict__
)
七、__str__,__repr__
#
####str#######
class
Foo:
def
__init__
(self,name,age):
self.name
=
name
self.age
=
age
def
__str__
(self):
return
'
名字是【%s】 年齡是【%s】
'
%
(self.name,self.age)
f1
=Foo(
'
simon
'
,18
)
print
(f1)
#
--str(f1)-->f1.__str__()
x=
str(f1)
print
(x)
#
#####repr######## ,當str與repr共存是,優先使用str
class
Foo:
def
__init__
(self,name,age):
self.name
=
name
self.age
=
age
#
def __str__(self):
#
return '這是str'
def
__repr__
(self):
return
'
名字是【%s】 年齡是【%s】
'
%
(self.name,self.age)
f1
=Foo(
'
simon
'
,20
)
#
repr(f1) ----->f1.__repr__()
print
(f1)
#
str(f1) ---->> f1.__str__() ------>f1.__repr__()
備注:
'''
str函數或者print函數--->obj.__str__()
repr或者交互式解釋器--->obj.__repr__()
如果__str__沒有被定義,那么就會使用__repr__來代替輸出
注意:這倆方法的返回值必須是字符串,否則拋出異常
'''
八、__format__
format_dic=
{
'
ymd
'
:
'
{0.year}{0.mon}{0.day}
'
,
'
m-d-y
'
:
'
{0.mon}-{0.day}-{0.year}
'
,
'
y:m:d
'
:
'
{0.year}:{0.mon}:{0.day}
'
}
class
Date:
def
__init__
(self,year,mon,day):
self.year
=
year
self.mon
=
mon
self.day
=
day
def
__format__
(self, format_spec):
print
(
'
我要執行啦
'
)
print
(
'
------->
'
,format_spec)
if
not
format_spec
or
format_spec
not
in
format_dic:
format_spec
=
'
ymd
'
fm
=
format_dic[format_spec]
return
fm.format(self)
d1
=Date(2018,12,30
)
format(d1)
#
d1.__format__()
print
(format(d1))
print
(format(d1,
'
ymd
'
))
print
(format(d1,
'
y:m:d
'
))
print
(format(d1,
'
m-d-y
'
))
print
(format(d1,
'
fsdrerewr
'
))
九、__slots__
1.
__slots__是什么
:是一個類變量,變量值可以是列表,元祖,或者可迭代對象,也可以是一個字符串(意味著所有實例只有一個數據屬性)
2
.引子:使用點來訪問屬性本質就是在訪問類或者對象的__dict__屬性字典(類的字典是共享的,而每個實例的是獨立的)
3
.為何使用__slots__:字典會占用大量內存,如果你有一個屬性很少的類,但是有很多實例,為了節省內存可以使用__slots__取代實例的__dict__
當你定義__slots__后,
__slots__就會為實例使用一種更加緊湊的內部表示
。實例通過一個很小的固定大小的數組來構建,而不是為每個實例定義一個
字典,這跟元組或列表很類似。在__slots__中列出的屬性名在內部被映射到這個數組的指定小標上。使用__slots__一個不好的地方就是我們不能再給
實例添加新的屬性了,只能使用在__slots__中定義的那些屬性名。
4.注意事項:
__slots__的很多特性都依賴于普通的基于字典的實現
。另外,定義了__slots__后的類不再 支持一些普通類特性了,比如多繼承。大多數情況下,你應該
只在那些經常被使用到 的用作數據結構的類上定義__slots__比如在程序中需要創建某個類的幾百萬個實例對象 。
關于__slots__的一個常見誤區是它可以作為一個封裝工具來防止用戶給實例增加新的屬性。盡管使用__slots__可以達到這樣的目的,但是這個并不是它的初衷。 更多的是用來作為一個內存優化工具。
class
Foo:
__slots__
=[
'
name
'
,
'
age
'
]
f1
=
Foo()
f1.name
=
'
alex
'
f1.age
=18
print
(f1.
__slots__
)
f2
=
Foo()
f2.name
=
'
egon
'
f2.age
=19
print
(f2.
__slots__
)
print
(Foo.
__dict__
)
#
f1與f2都沒有屬性字典__dict__了,統一歸__slots__管,節省內存
十、__doc__
class
Foo:
'
我是描述信息
'
pass
class
Bar(Foo):
pass
#
print(Foo.__doc__) #該屬性無法繼承給子類
print
(Bar.
__dict__
)
print
(Bar.
__doc__
)
#
該屬性無法繼承給子類
十一、__module__和__class__
__module__表示當前操作的對象在哪個模塊
—class__ 表示當前操作的對象的類是什么
lib/simon.py
#
!/usr/bin/env python
#
_*_ coding:utf-8 _*_
class
C:
def
__init__
(self):
self.name
=
'
simon
'
aa.py
from
lib.simon
import
C
obj
=
C()
print
(obj.
__module__
)
#
輸出 lib.simon,即:輸出模塊
print
(obj.
__class__
)
#
輸出lib.simon.C,即:輸出類
十二、__del__
class
Foo:
def
__init__
(self,name):
self.name
=
name
def
__del__
(self):
print
(
'
我執行啦
'
)
f1
=Foo(
'
simon
'
)
#
del f1 #刪除實例會觸發__del__
del
f1.name
#
刪除實例的屬性不會觸發__del__
print
(
'
--------------------->
'
)
#
程序運行完畢會自動回收內存,觸發__del__
十三、__call__
對象后面加括號,觸發執行。
注:構造方法的執行是由創建對象觸發的,即:對象 = 類名() ;而對于 __call__ 方法的執行是由對象后加括號觸發的,即:對象() 或者 類()()
class
Foo:
def
__call__
(self, *args, **
kwargs):
print
(
'
實例執行啦 obj()
'
)
f1
=
Foo()
f1()
#
f1的類Foo 下的__call__
Foo()
#
Foo的類 xxx下的__call__
十四、__next__和__iter__實現迭代器協議
1、迭代器協議是指:對象必須提供一個Next方法,執行該方法要么返回迭代中的下一項,要么就引起一個StopIteration異常,以終止迭代(只能往后走不能往前退)
2、可迭代對象:實現了迭代器協議的對象(如何實現:對象內部定義一個__iter__()方法)
3、協議是一種約定,可迭代對象實現了迭代器協議,python內部工具(如for循環,sum,min,max函數等)使用迭代器協議訪問對象。
class
Foo:
def
__init__
(self,n):
self.n
=
n
def
__iter__
(self):
return
self
def
__next__
(self):
if
self.n == 13
:
raise
StopIteration(
'
終止了
'
)
self.n
+=1
return
self.n
#
l=list('simon')
#
for i in l:
#
print(i)
f1
=Foo(10
)
#
print(f1.__next__())
#
print(f1.__next__())
#
print(f1.__next__())
for
i
in
f1:
#
iter(f1)------------>f1.__iter__()
print
(i)
斐波那契數列:
class
Fib:
def
__init__
(self):
self._a
=1
self._b
=1
def
__iter__
(self):
return
self
def
__next__
(self):
if
self._a > 100
:
raise
StopIteration(
'
終止了
'
)
self._a,self._b
=self._b,self._a +
self._b
return
self._a
f1
=
Fib()
print
(next(f1))
print
(next(f1))
print
(next(f1))
print
(next(f1))
print
(next(f1))
print
(
'
============================
'
)
for
i
in
f1:
print
(i)
十五、描述符(_get_,_set_,_delete_)
1、什么是描述符
描述符本質就是一個新式類,在這個新式類中,至少實現了__get__(),__set__(),__delete__()中的一個,這也被稱為描述符協議
__get__():調用一個屬性時,觸發
__set__():為一個屬性賦值時,觸發
__delete__():采用del刪除屬性時,觸發
class
Foo:
def
__get__
(self, instance, owner):
print
(
'
=====>get方法
'
)
def
__set__
(self, instance, value):
print
(
'
=====>set方法
'
)
def
__delete__
(self, instance):
print
(
'
=====>delete方法
'
)
class
Bar:
x
=Foo()
#
在何地?
#
在何時
b1=
Bar()
b1.x
b1.x
=1
del
b1.x
f1
=
Foo()
f1.name
=
'
simon
'
print
(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刪除...
'
)
class
People:
name
=
Str()
def
__init__
(self,name,age):
#
name被Str類代理,age被Int類代理,
self.name=
name
self.age
=
age
#
基于上面的演示,我們已經知道,在一個類中定義描述符它就是一個類屬性,存在于類的屬性字典中,而不是實例的屬性字典
#
那既然描述符被定義成了一個類屬性,直接通過類名也一定可以調用吧,沒錯
People.name
#
恩,調用類屬性name,本質就是在調用描述符Str,觸發了__get__()
People.name
=
'
egon
'
#
那賦值呢,我去,并沒有觸發__set__()
del
People.name
#
趕緊試試del,我去,也沒有觸發__delete__()
#
結論:描述符對類沒有作用-------->傻逼到家的結論
'''
原因:描述符在使用時被定義成另外一個類的類屬性,因而類屬性比二次加工的描述符偽裝而來的類屬性有更高的優先級
People.name #恩,調用類屬性name,找不到就去找描述符偽裝的類屬性name,觸發了__get__()
People.name='egon' #那賦值呢,直接賦值了一個類屬性,它擁有更高的優先級,相當于覆蓋了描述符,肯定不會觸發描述符的__set__()
del People.name #同上
'''
數據描述符>實例屬性
#
描述符Str
class
Str:
def
__get__
(self, instance, owner):
print
(
'
Str調用
'
)
def
__set__
(self, instance, value):
print
(
'
Str設置...
'
)
def
__delete__
(self, instance):
print
(
'
Str刪除...
'
)
class
People:
name
=
Str()
def
__init__
(self,name,age):
#
name被Str類代理,age被Int類代理,
self.name=
name
self.age
=
age
p1
=People(
'
egon
'
,18
)
#
如果描述符是一個數據描述符(即有__get__又有__set__),那么p1.name的調用與賦值都是觸發描述符的操作,于p1本身無關了,相當于覆蓋了實例的屬性
p1.name=
'
egonnnnnn
'
p1.name
print
(p1.
__dict__
)
#
實例的屬性字典中沒有name,因為name是一個數據描述符,優先級高于實例屬性,查看/賦值/刪除都是跟描述符有關,與實例無關了
del
p1.name
實例屬性>非數據描述符
class
Foo:
def
func(self):
print
(
'
我胡漢三又回來了
'
)
f1
=
Foo()
f1.func()
#
調用類的方法,也可以說是調用非數據描述符
#
函數是一個非數據描述符對象(一切皆對象么)
print
(dir(Foo.func))
print
(hasattr(Foo.func,
'
__set__
'
))
print
(hasattr(Foo.func,
'
__get__
'
))
print
(hasattr(Foo.func,
'
__delete__
'
))
#
有人可能會問,描述符不都是類么,函數怎么算也應該是一個對象啊,怎么就是描述符了
#
笨蛋哥,描述符是類沒問題,描述符在應用的時候不都是實例化成一個類屬性么
#
函數就是一個由非描述符類實例化得到的對象
#
沒錯,字符串也一樣
f1.func
=
'
這是實例屬性啊
'
print
(f1.func)
del
f1.func
#
刪掉了非數據
f1.func()
再次驗證:實例屬性>非數據描述符
class
Foo:
def
__set__
(self, instance, value):
print
(
'
set
'
)
def
__get__
(self, instance, owner):
print
(
'
get
'
)
class
Room:
name
=
Foo()
def
__init__
(self,name,width,length):
self.name
=
name
self.width
=
width
self.length
=
length
#
name是一個數據描述符,因為name=Foo()而Foo實現了get和set方法,因而比實例屬性有更高的優先級
#
對實例的屬性操作,觸發的都是描述符的
r1=Room(
'
廁所
'
,1,1
)
r1.name
r1.name
=
'
廚房
'
class
Foo:
def
__get__
(self, instance, owner):
print
(
'
get
'
)
class
Room:
name
=
Foo()
def
__init__
(self,name,width,length):
self.name
=
name
self.width
=
width
self.length
=
length
#
name是一個非數據描述符,因為name=Foo()而Foo沒有實現set方法,因而比實例屬性有更低的優先級
#
對實例的屬性操作,觸發的都是實例自己的
r1=Room(
'
廁所
'
,1,1
)
r1.name
r1.name
=
'
廚房
'
?總結:
1、描述符本身應該定義成新式類,被代理的類也應該是新式類
2、必須把描述符定義成這個類的代理,不能為定義到構造函數中
3、要嚴格遵循優先級,優先級由高到低分別
?
十六、__enter__和__exit__
操作文件對象的時候:
with open(
'
text.txt
'
,
'
r
'
) as f:
'
代碼塊
'
上述叫做上下文管理協議,即with語句,為了讓一個對象兼容with語句,必須在這個對象的類中聲明__enter__和__exit__方法
class
Foo:
def
__init__
(self,name):
self.name
=
name
def
__enter__
(self):
print
(
'
執行enter
'
)
return
self
def
__exit__
(self, exc_type, exc_val, exc_tb):
print
(
'
執行exit
'
)
#
f=Open('a.txt')
with Foo(
'
a.txt
'
) as f:
print
(f)
print
(f.name)
print
(
'
00000000000000000000
'
)
上述代碼分析:
class
Foo:
def
__init__
(self,name):
self.name
=
name
def
__enter__
(self):
print
(
'
執行enter
'
)
return
self
def
__exit__
(self, exc_type, exc_val, exc_tb):
print
(
'
執行exit
'
)
print
(exc_type)
print
(exc_val)
print
(exc_tb)
return
True
#
f=Open('a.txt')
with Foo(
'
a.txt
'
) as f:
print
(f)
print
(asfdreevergewafa)
#
觸發__exit__
print
(f.name)
print
(
'
00000000000000000000
'
)
#
with obj as f:
'
代碼塊
'
1、with obj ---->>觸發obj.
__enter__
(),拿到返回值
2、as f--------->f=
返回值、
3、with obj as f 等同于 f=obj.
__enter__
()
4
、執行代碼塊
一:沒有異常的情況下,整個代碼塊運行完畢后去觸發__exit__,它的三個參數都為None
二:有異常的情況下,從異常出現的位置直接觸發__exit__
a: 如果__exit__的返回值為True,代表吞掉了異常
b: 如果__exit__的返回值不為True,代表了吐出了異常
c:
__exit__的運行完畢就代表了整個with語句的執行完畢
總結:
1.使用with語句的目的就是把代碼塊放入with中執行,with結束后,自動完成清理工作,無須手動干預
2.在需要管理一些資源比如文件,網絡連接和鎖的編程環境中,可以在__exit__中定制自動釋放資源的機制,你無須再去關系這個問題,這將大有用處
?
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061
微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元

