1. 程式人生 > >python面向對象( item系列,__enter__ 和__exit__,__call__方法,元類)

python面向對象( item系列,__enter__ 和__exit__,__call__方法,元類)

屬性 eba callable 好處 繼承方式 類的創建 完成 __weak 依次

python面向對象進階(下)

item系列 __slots__方法 __next__ 和 __iter__實現叠代器 析構函數 上下文管理協議 元類
一、item系列 把對象操作屬性模擬成字典的格式。
例如:對象名[‘key‘] = value

技術分享
class Foo:
    def __init__(self,name):
        self.name = name
    def __getitem__(self, item):
        return self.__dict__[item]
    def __setitem__(self, key, value):
        self.__dict__[key] = value
    def __delitem__(self, key):
        self.__dict__.pop(key)

f = Foo(‘egon‘)
f[‘age‘] = 18
print(f.__dict__)
del f[‘age‘]
print(f.__dict__)
print(f[‘name‘])
#print(f[‘sex‘])

#執行結果:
{‘name‘: ‘egon‘, ‘age‘: 18}
{‘name‘: ‘egon‘}
egon
技術分享

二、__slots__方法:

1.__slots__是什麽: 是一個類變量,變量值可以是列表,元組,或者可叠代對象,也可以是一個字符串(意味著所有實例只有一個數據屬性)

2.使用點來訪問屬性本質,就是在訪問類或者對象的__dict__屬性字典(類的字典是共享的,而每個實例的是獨立的)

3.為何使用__slots__:字典會占用大量內存,如果你有一個屬性很少的類,但是有很多實例,為了節省內存可以使用__slots__取代實例的__dict__
當你定義__slots__後,__slots__就會為實例使用一種更加緊湊的內部表示。實例通過一個很小的固定大小的數組來構建,而不再是為每個實例定義一個字典,這跟元組或列表很類似。
__slots__中列出的屬性名在內部被映射到這個數組的指定小標上。使用__slots__一個不好的地方就是我們不能再給實例添加新的屬性了,只能使用在__slots__中定義的那些屬性名。

4.註意事項:__slots__的很多特性都依賴於普通的基於字典的實現。另外,定義了__slots__後的類不再 支持一些普通類特性了,比如多繼承。大多數情況下,你應該只在那些經常被使用到的用作數據結構的類上定義__slots__比如在程序中需要創建某個類的幾百萬個實例對象 。

5、好處:節省內存:類內部指定屬性,對象就只能建立所對應的屬性。不再有屬性字典__dict__,統一歸__slots__管。

關於__slots__的一個常見誤區是它可以作為一個封裝工具來防止用戶給實例增加新的屬性。盡管使用__slots__可以達到這樣的目的,但是這個並不是它的初衷。更多的是用來作為一個內存優化工具。

技術分享
class People:
    __slots__ = [‘x‘,‘y‘,‘z‘]

p = People()
print(People.__dict__)  #查看類的字典,找到方法!

p.x = 1
p.y = 2
p.z = 3
print(p.x,p.y,p.z)

p1 = People()
p1.x = 10
p1.y = 20
p1.z = 30
print(p1.x,p1.y,p1.z)
print(p1.__dict__) #會報錯!

#執行結果:
{‘__module__‘: ‘__main__‘, ‘__slots__‘: [‘x‘, ‘y‘, ‘z‘], ‘x‘: <member ‘x‘ of ‘People‘ objects>, ‘y‘: <member ‘y‘ of ‘People‘ objects>, ‘z‘: <member ‘z‘ of ‘People‘ objects>, ‘__doc__‘: None}
1 2 3
10 20 30
Traceback (most recent call last):
  File "F:/py_fullstack_s4/day32/__slots__的方法.py", line 18, in <module>
    print(p1.__dict__)
AttributeError: ‘People‘ object has no attribute ‘__dict__‘
技術分享

三、__next__ 和 __iter__實現叠代器

技術分享
from collections import Iterable,Iterator
class Foo:
    def __init__(self,start):
        self.start = start

    def __iter__(self): #可叠代方法,將對象變成叠代器,故返回其自己
        return self
    def __next__(self):  #對叠代器取值
        if self.start > 10:
            raise StopIteration
        n = self.start
        self.start += 1
        return n

f = Foo(0)
print(isinstance(Foo,Iterable))  #判斷是否是可叠代對象
print(isinstance(Foo,Iterator))  #判斷是否是叠代器

print(isinstance(f,Iterable))    #判斷是否是可叠代對象
print(isinstance(f,Iterator))    #判斷是否是叠代器

for i in f :
    print(i)

#執行結果:
False
False
True
True
0
1
2
3
4
5
6
7
8
9
10
技術分享

  定義類,實現range()的方法!

技術分享
class Range:
    def __init__(self,start,end):
        self.start = start
        self.end = end
    def __iter__(self):
        return self
    def __next__(self):
        if self.start == self.end:
            raise StopIteration
        n = self.start
        self.start += 1
        return n
for i in Range(0,3):
    print(i)

#執行結果:
0
1
2
技術分享

callable() 判斷類是否可調用

四、 __doc__描述信息,該屬性無法被繼承,是對本函數或類的描述!沒寫打印None

   __class__和__module__
__module__ 表示當前操作的對象在哪個模塊
__class__ 表示當前操作的對象的類是什麽

技術分享
class Foo:
    pass
class A(Foo):
    pass
a = A()
print(a.__class__)
print(a.__module__)

#執行結果:
<class ‘__main__.A‘>
__main__
技術分享

五、__del__ 析構函數

刪除對象或是對象執行完回收,引用基數為0,的時候,自動觸發執行,將對象在內存中釋放。
註:此方法一般無須定義,因為Python是一門高級語言,程序員在使用時無需關心內存的分配和釋放,因為此工作都是交給Python解釋器來執行,所以,析構函數的調用是由解釋器在進行垃圾回收時自動觸發執行的。

在類中定義 __del__ 一般是寫入文件關閉,或是數據庫關閉的這種終止操作。只要是函數沒有完全運行結束,就不會將內存全部回收,執行完誰回收誰。

技術分享
import time
class Open:
    def __init__(self,filepath,mode = ‘r‘,encode = ‘utf-8‘):
        self.f = open(filepath,mode=mode,encoding=encode)

    def write(self):
        pass
    def __getattr__(self, item):
        return getattr(self.f,item)

    def __del__(self):
        print(‘=---->del‘)
        self.f.close()
f = Open(‘a.txt‘,‘r+‘)
#沒有刪除命令,也會自動執行!當沒有代碼要執行,引用基數為0的時候,就會觸發。
# f1 = f  #定義其他的引用
# del f #先執行刪除命令,再打印下邊的內容
print(‘=-====>‘)
#執行方式:
# =-====>
# =---->del

#先執行刪除命令,再打印下邊的內容
del f #先執行刪除命令,再打印下邊的內容
print(‘=-====>‘) #沒有刪除命令,也會自動執行!當沒有代碼要執行,引用基數為0的時候,就會觸發。
#執行方式:
# =---->del
# =-====>

#引用基數不為0的情況
f1 = f  #定義其他的引用
del f #先執行刪除命令,再打印下邊的內容
print(‘=-====>‘)
#執行方式:
# =-====>
# =---->del
技術分享

六、上下文管理協議 (with 方法)
__enter__ 和__exit__
用途或者說好處:

  1.使用with語句的目的就是把代碼塊放入with中執行,with結束後,自動完成清理工作,無須手動幹預

  2.在需要管理一些資源比如文件,網絡連接和鎖的編程環境中,可以在__exit__中定制自動釋放資源的機制,你無須再去關系這個問題,這將大有用處

技術分享
# __enter__和__exit__

class Foo:
    def __enter__(self):  #with 拿到一個對象,觸發enter方法
        print(‘enter‘)
        return 11111
    def __exit__(self, exc_type, exc_val, exc_tb): #文件打開執行完畢後,執行exit方法
        print(‘exit‘)
        print(‘exc_type‘,exc_type)  #異常的類型
        print(‘exc_val‘,exc_val)    #異常的值
        print(‘exc_tb‘,exc_tb)      #異常的內存地址
        return True #清空所有異常,拋異常之後,with後的內容正常執行。
with Foo() as obj:  #執行Foo().__enter__方法得到一個返回值,然後將這個值賦給obj。
    #出現with語句, 對象的__enter__被觸發, 有返回值則賦值給as聲明的變量
    print(‘with Foo 的子代碼塊‘,obj) #執行with模塊字代碼
    raise NameError(‘名字沒有定義!‘)  #只要是報異常,沒有處理的話,就意味著with執行的字代碼塊運行完。觸發exit方法,這之後的代碼就不會再執行。
    print(‘##############‘)
print(‘**************‘)

#執行結果:
enter
with Foo 的子代碼塊 11111
exit
exc_type <class ‘NameError‘>
exc_val 名字沒有定義!
exc_tb <traceback object at 0x00000000028EE948>
**************
技術分享

例子:實現上下文的管理協議:(with方法打開文件執行操作!)

技術分享
class Open:
    def __init__(self,filepath,mode,encode=‘utf-8‘):
        self.f=open(filepath,mode=mode,encoding=encode)
        self.filepath=filepath
        self.mode=mode
        self.encoding=encode

    def write(self,line):
        print(‘write‘)
        self.f.write(line)

    def __getattr__(self, item):
        return getattr(self.f,item)

    def __enter__(self): #with 對象 就會觸發對象下的該方法
        return self  #將對象返回 write_file=Open(‘aaaaa.txt‘,‘w‘)
        #return self.f #這就是返回真實的open方法,字代碼塊的方法都可以使用,但是就不再是類Open的使用。
    def __exit__(self, exc_type, exc_val, exc_tb):#文件結束清理
        self.f.close()
        return True

with Open(‘aaaaa.txt‘,‘w‘) as write_file: #變相的實例化 write_file=Open(‘aaaaa.txt‘,‘w‘)拿到產生的文件句柄
    write_file.write(‘123123123123123\n‘)
    write_file.write(‘123123123123123\n‘)
    write_file.write(‘123123123123123\n‘)
技術分享

七、__call__方法

對象後面加括號,觸發執行。

註:構造方法的執行是由創建對象觸發的,即:對象 = 類名() ;而對於 __call__ 方法的執行是由對象後加括號觸發的,即:對象() 或者 類()()

技術分享
class Foo:
    def __init__(self,name):
        self.name = name
    def __call__(self, *args, **kwargs):
        print(‘__call__‘)

obj = Foo(‘egon‘)  # 執行 __init__
#obj()  # 執行 __call__ 對象對去找有沒有__call__的綁定方法,有加()就能運行!
#查看是否為可調用對象(可調用對象:名字後加()就能運行)
print(callable(Foo))
print(callable(obj))

#執行結果:
True
True

#若是隱去__call__功能,查看結果
class Foo:
    def __init__(self,name):
        self.name = name
    # def __call__(self, *args, **kwargs):
    #     print(‘__call__‘)

obj = Foo(‘egon‘)  # 執行 __init__
#obj()  # 執行 __call__ 對象對去找有沒有__call__的綁定方法,有加()就能運行!
#查看是否為可調用對象(可調用對象:名字後加()就能運行)
print(callable(Foo))
print(callable(obj))

#執行結果:
True
False
技術分享

八、元類

1 引子

1 class Foo:
2     pass
3 
4 f1=Foo() #f1是通過Foo類實例化的對象

  python中一切皆是對象,類本身也是一個對象,當使用關鍵字class的時候,python解釋器在加載class的時候就會創建一個對象(這裏的對象指的是類而非類的實例)

上例可以看出f1是由Foo這個類產生的對象,而Foo本身也是對象,那它又是由哪個類產生的呢?

1 #type函數可以查看類型,也可以用來查看對象的類,二者是一樣的
2 print(type(f1)) # 輸出:<class ‘__main__.Foo‘>     表示,obj 對象由Foo類創建
3 print(type(Foo)) # 輸出:<type ‘type‘>  

2 什麽是元類?

元類是類的類,是類的模板

元類是用來控制如何創建類的,正如類是創建對象的模板一樣

元類的實例為類,正如類的實例為對象(f1對象是Foo類的一個實例Foo類是 type 類的一個實例)

type是python的一個內建元類,用來直接控制生成類,python中任何class定義的類其實都是type類實例化的對象

3 創建類的兩種方式

  方法一:

技術分享
class Foo:
    x =1
    def run(self):
        pass

print(Foo)
print(type(Foo))
技術分享

  方法二:

技術分享
#type稱為元類,是所有類的類,控制類的。利用type模擬class關鍵字的創建類的過程。
def run(self):
    print(‘%s is running‘%self.name)
class_name = ‘Bar‘  #類名
bases=(object,)     #繼承方式
class_dic = {‘x‘:1,‘run‘:run}  #名稱空間 屬性方法
#語法:變量名 = type(類名,繼承關系(元組),屬性方法(字典)) class 關鍵字創建類,其實質就是type()封裝的方法!
Bar = type(class_name,bases,class_dic)  #自定義生成一個類
print(Bar)  #查看類型
print(type(Bar)) #查看元類
技術分享

4 一個類沒有聲明自己的元類,默認他的元類就是type,除了使用元類type,用戶也可以通過繼承type來自定義元類(順便我們也可以瞅一瞅元類如何控制類的創建,工作流程是什麽)

(1)通過元類給類添加一些條件判斷方法(查看工作流程)

技術分享
class Mymeta(type): #定義元類繼承type
     def __init__(self,class_name,class_bases,class_dic):
         #實例化就會調用執行默認的方法,和用type直接創建一個類一致。(類名,繼承關系,方法)
         print(self)         #對比打印驗證
         print(class_name)    #對比打印驗證
         print(class_bases)   #對比打印驗證
         print(class_dic)    #對比打印驗證
         #對創建類,不寫__doc__方法的人進行提示
         for key in class_dic:  #可在元類初始化這裏加上對下邊 子類 的判斷驗證方法
            if not callable(class_dic[key]):continue  #判斷類方法中有沒有可調用方法,沒有繼續
            if not class_dic[key].__doc__:  #判斷方法類中__doc__對應的值是否為None
                raise TypeError(‘小子,你沒寫註釋,趕緊去寫‘)

# type.__init__(self,class_name,class_bases,class_dic)  #執行的上述方法實際上就是在調用type()的方法
class Foo(metaclass=Mymeta):#將類foo看成元類的對象,加()運行就是在進行實例化
    x=1
    def run(self):
        ‘run function‘
        print(‘running‘)
# Foo=Mymeta(‘Foo‘,(object,),{‘x‘:1,‘run‘:run})#上述利用class方法定義Foo類與用type()定義一致。
print(Foo.__dict__)

#執行結果:
<class ‘__main__.Foo‘>
Foo
()
{‘__module__‘: ‘__main__‘, ‘__qualname__‘: ‘Foo‘, ‘x‘: 1, ‘run‘: <function Foo.run at 0x00000000022BC950>}
{‘__module__‘: ‘__main__‘, ‘x‘: 1, ‘run‘: <function Foo.run at 0x00000000022BC950>, ‘__dict__‘: <attribute ‘__dict__‘ of ‘Foo‘ objects>, ‘__weakref__‘: <attribute ‘__weakref__‘ of ‘Foo‘ objects>, ‘__doc__‘: None}
技術分享

(2)利用元類,定義產生一個類的完整過程:

技術分享
class Mymeta(type): #定義元類
     def __init__(self,class_name,class_bases,class_dic):#始終明確 self 就是Foo
            pass #不定義就走type()的內部方法
     def __call__(self, *args, **kwargs): #類實例化就能調用執行其實質上是執行了__call__方法
        # print(self)
        obj=self.__new__(self)  #類Foo實例化f, Foo執行__new__方法,先產生一個空對象
        self.__init__(obj,*args,**kwargs) #obj.name=‘egon‘
            #上一行代碼是在 給產生的空對象傳值,調用Foo下的__init__方法,也就是定義的類 Foo中的__init__方法
        return obj   #空對象拿到值之後,產生一個新的對象obj,然後獲取這個值,將這個值返回。
class Foo(metaclass=Mymeta):
    x=1
    def __init__(self,name):  #類內給實例化對象定義的初始屬性
        self.name=name #obj.name=‘egon‘
    def run(self):
        ‘run function‘
        print(‘running‘)
# print(Foo.__dict__)

f=Foo(‘egon‘)  #類實例化
print(f)
print(f.name)

#執行結果:
<__main__.Foo object at 0x0000000002280128>
egon
技術分享

註意點:

技術分享
#元類總結
class Mymeta(type):
    def __init__(self,name,bases,dic):
        print(‘===>Mymeta.__init__‘)


    def __new__(cls, *args, **kwargs):
        print(‘===>Mymeta.__new__‘)
        return type.__new__(cls,*args,**kwargs)

    def __call__(self, *args, **kwargs):
        print(‘aaa‘)
        obj=self.__new__(self)
        self.__init__(self,*args,**kwargs)
        return obj

class Foo(object,metaclass=Mymeta):
    def __init__(self,name):
        self.name=name
    def __new__(cls, *args, **kwargs):
        return object.__new__(cls)

‘‘‘
需要記住一點:名字加括號的本質(即,任何name()的形式),都是先找到name的爹,然後執行:爹.__call__

而爹.__call__一般做兩件事:
1.調用name.__new__方法並返回一個對象
2.進而調用name.__init__方法對兒子name進行初始化
‘‘‘

‘‘‘
class 定義Foo,並指定元類為Mymeta,這就相當於要用Mymeta創建一個新的對象Foo,於是相當於執行
Foo=Mymeta(‘foo‘,(...),{...})
因此我們可以看到,只定義class就會有如下執行效果
===>Mymeta.__new__
===>Mymeta.__init__
實際上class Foo(metaclass=Mymeta)是觸發了Foo=Mymeta(‘Foo‘,(...),{...})操作,
遇到了名字加括號的形式,即Mymeta(...),於是就去找Mymeta的爹type,然後執行type.__call__(...)方法
於是觸發Mymeta.__new__方法得到一個具體的對象,然後觸發Mymeta.__init__方法對對象進行初始化
‘‘‘

‘‘‘
obj=Foo(‘egon‘)
的原理同上
‘‘‘

‘‘‘
總結:元類的難點在於執行順序很繞,其實我們只需要記住兩點就可以了
1.誰後面跟括號,就從誰的爹中找__call__方法執行
type->Mymeta->Foo->obj
Mymeta()觸發type.__call__
Foo()觸發Mymeta.__call__
obj()觸發Foo.__call__
2.__call__內按先後順序依次調用兒子的__new__和__init__方法
‘‘‘
技術分享

python面向對象( item系列,__enter__ 和__exit__,__call__方法,元類)