十一 __doc__

class Foo: '我是描述信息' pass print(Foo.__doc__)

class Foo: '我是描述信息' pass class Bar(Foo): pass print(Bar.__doc__) #該屬性無法繼承給子類
十二 __module__和__class__
__module__ 表示當前操作的對象在那個模塊
__class__ 表示當前操作的對象的類是什么

#!/usr/bin/env python # -*- coding:utf-8 -*- class C: def __init__(self): self.name = ‘SB' lib/aa.py

from lib.aa import C obj = C() print obj.__module__ # 輸出 lib.aa,即:輸出模塊 print obj.__class__ # 輸出 lib.aa.C,即:輸出類
十三 __del__
析構方法,當對象在內存中被釋放時,自動觸發執行。
注:如果產生的對象僅僅只是python程序級別的(用戶級),那么無需定義__del__,如果產生的對象的同時還會向操作系統發起系統調用,即一個對象有用戶級與內核級兩種資源,比如(打開一個文件,創建一個數據庫鏈接),則必須在清除對象的同時回收系統資源,這就用到了__del__

class Foo: def __del__(self): print('執行我啦') f1=Foo() del f1 print('------->') #輸出結果 執行我啦 -------> 簡單示范

class Foo: def __del__(self): print('執行我啦') f1=Foo() # del f1 print('------->') #輸出結果 -------> 執行我啦 #為何啊??? 挖坑埋了你
典型的應用場景:
創建數據庫類,用該類實例化出數據庫鏈接對象,對象本身是存放於用戶空間內存中,而鏈接則是由操作系統管理的,存放於內核空間內存中
當程序結束時,python只會回收自己的內存空間,即用戶態內存,而操作系統的資源則沒有被回收,這就需要我們定制__del__,在對象被刪除前向操作系統發起關閉數據庫鏈接的系統調用,回收資源
這與文件處理是一個道理:
f=open('a.txt') #做了兩件事,在用戶空間拿到一個f變量,在操作系統內核空間打開一個文件 del f #只回收用戶空間的f,操作系統的文件還處於打開狀態 #所以我們應該在del f之前保證f.close()執行,即便是沒有del,程序執行完畢也會自動del清理資源,於是文件操作的正確用法應該是 f=open('a.txt') 讀寫... f.close() 很多情況下大家都容易忽略f.close,這就用到了with上下文管理
十四 __enter__和__exit__
我們知道在操作文件對象的時候可以這么寫
1 with open('a.txt') as f: 2 '代碼塊'
上述叫做上下文管理協議,即with語句,為了讓一個對象兼容with語句,必須在這個對象的類中聲明__enter__和__exit__方法

class Open: def __init__(self,name): self.name=name def __enter__(self): print('出現with語句,對象的__enter__被觸發,有返回值則賦值給as聲明的變量') # return self def __exit__(self, exc_type, exc_val, exc_tb): print('with中代碼塊執行完畢時執行我啊') with Open('a.txt') as f: print('=====>執行代碼塊') # print(f,f.name) 上下文管理協議
__exit__()中的三個參數分別代表異常類型,異常值和追溯信息,with語句中代碼塊出現異常,則with后的代碼都無法執行

class Open: def __init__(self,name): self.name=name def __enter__(self): print('出現with語句,對象的__enter__被觸發,有返回值則賦值給as聲明的變量') def __exit__(self, exc_type, exc_val, exc_tb): print('with中代碼塊執行完畢時執行我啊') print(exc_type) print(exc_val) print(exc_tb) with Open('a.txt') as f: print('=====>執行代碼塊') raise AttributeError('***着火啦,救火啊***') print('0'*100) #------------------------------->不會執行
如果__exit()返回值為True,那么異常會被清空,就好像啥都沒發生一樣,with后的語句正常執行

class Open: def __init__(self,name): self.name=name def __enter__(self): print('出現with語句,對象的__enter__被觸發,有返回值則賦值給as聲明的變量') def __exit__(self, exc_type, exc_val, exc_tb): print('with中代碼塊執行完畢時執行我啊') print(exc_type) print(exc_val) print(exc_tb) return True with Open('a.txt') as f: print('=====>執行代碼塊') raise AttributeError('***着火啦,救火啊***') print('0'*100) #------------------------------->會執行

class Open: def __init__(self,filepath,mode='r',encoding='utf-8'): self.filepath=filepath self.mode=mode self.encoding=encoding def __enter__(self): # print('enter') self.f=open(self.filepath,mode=self.mode,encoding=self.encoding) return self.f def __exit__(self, exc_type, exc_val, exc_tb): # print('exit') self.f.close() return True def __getattr__(self, item): return getattr(self.f,item) with Open('a.txt','w') as f: print(f) f.write('aaaaaa') f.wasdf #拋出異常,交給__exit__處理 練習:模擬Open
用途或者說好處:
1.使用with語句的目的就是把代碼塊放入with中執行,with結束后,自動完成清理工作,無須手動干預
2.在需要管理一些資源比如文件,網絡連接和鎖的編程環境中,可以在__exit__中定制自動釋放資源的機制,你無須再去關系這個問題,這將大有用處
十五 __call__
對象后面加括號,觸發執行。
注:構造方法的執行是由創建對象觸發的,即:對象 = 類名() ;而對於 __call__ 方法的執行是由對象后加括號觸發的,即:對象() 或者 類()()

class Foo: def __init__(self): pass def __call__(self, *args, **kwargs): print('__call__') obj = Foo() # 執行 __init__ obj() # 執行 __call__
十六 metaclass
1知識儲備
exec:三個參數 參數一:字符串形式的命令 參數二:全局作用域(字典形式),如果不指定,默認為globals() 參數三:局部作用域(字典形式),如果不指定,默認為locals()

#可以把exec命令的執行當成是一個函數的執行,會將執行期間產生的名字存放於局部名稱空間中 g={ 'x':1, 'y':2 } l={} exec(''' global x,z x=100 z=200 m=300 ''',g,l) print(g) #{'x': 100, 'y': 2,'z':200,......} print(l) #{'m': 300} exec的使用
2引子(類也是對象)
class Foo: pass 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'>
元類是類的類,是類的模板
元類是用來控制如何創建類的,正如類是創建對象的模板一樣,而元類的主要目的是為了控制類的創建行為
元類的實例化的結果為我們用class定義的類,正如類的實例為對象(f1對象是Foo類的一個實例,Foo類是 type 類的一個實例)
type是python的一個內建元類,用來直接控制生成類,python中任何class定義的類其實都是type類實例化的對象
4創建類的兩種方式
方式一:使用class關鍵字
class Chinese(object): country='China' def __init__(self,name,age): self.name=name self.age=age def talk(self): print('%s is talking' %self.name)
方式二:就是手動模擬class創建類的過程):將創建類的步驟拆分開,手動去創建
#准備工作: #創建類主要分為三部分 類名 類的父類 類體 #類名 class_name='Chinese' #類的父類 class_bases=(object,) #類體 class_body=""" country='China' def __init__(self,name,age): self.name=name self.age=age def talk(self): print('%s is talking' %self.name) """
步驟一(先處理類體->名稱空間):類體定義的名字都會存放於類的名稱空間中(一個局部的名稱空間),我們可以事先定義一個空字典,然后用exec去執行類體的代碼(exec產生名稱空間的過程與真正的class過程類似,只是后者會將__開頭的屬性變形),生成類的局部名稱空間,即填充字典
class_dic={} exec(class_body,globals(),class_dic) print(class_dic) #{'country': 'China', 'talk': <function talk at 0x101a560c8>, '__init__': <function __init__ at 0x101a56668>}
步驟二:調用元類type(也可以自定義)來產生類Chinense
Foo=type(class_name,class_bases,class_dic) #實例化type得到對象Foo,即我們用class定義的類Foo print(Foo) print(type(Foo)) print(isinstance(Foo,type)) ''' <class '__main__.Chinese'> <class 'type'> True '''
我們看到,type 接收三個參數:
-
第 1 個參數是字符串 ‘Foo’,表示類名
-
第 2 個參數是元組 (object, ),表示所有的父類
-
第 3 個參數是字典,這里是一個空字典,表示沒有定義屬性和方法
補充:若Foo類有繼承,即class Foo(Bar):.... 則等同於type('Foo',(Bar,),{})
5 自定義元類控制類的行為
#一個類沒有聲明自己的元類,默認他的元類就是type,除了使用元類type,用戶也可以通過繼承type來自定義元類(順便我們也可以瞅一瞅元類如何控制類的行為,工作流程是什么)

#!!!如果你拷貝不注明出處的話,以后老子都不寫了!!! #知識儲備: #產生的新對象 = object.__new__(繼承object類的子類) #步驟一:如果說People=type(類名,類的父類們,類的名稱空間),那么我們定義元類如下,來控制類的創建 class Mymeta(type): # 繼承默認元類的一堆屬性 def __init__(self, class_name, class_bases, class_dic): if '__doc__' not in class_dic or not class_dic.get('__doc__').strip(): raise TypeError('必須為類指定文檔注釋') if not class_name.istitle(): raise TypeError('類名首字母必須大寫') super(Mymeta, self).__init__(class_name, class_bases, class_dic) class People(object, metaclass=Mymeta): country = 'China' def __init__(self, name, age): self.name = name self.age = age def talk(self): print('%s is talking' % self.name) #步驟二:如果我們想控制類實例化的行為,那么需要先儲備知識__call__方法的使用 class People(object,metaclass=type): def __init__(self,name,age): self.name=name self.age=age def __call__(self, *args, **kwargs): print(self,args,kwargs) # 調用類People,並不會出發__call__ obj=People('egon',18) # 調用對象obj(1,2,3,a=1,b=2,c=3),才會出發對象的綁定方法obj.__call__(1,2,3,a=1,b=2,c=3) obj(1,2,3,a=1,b=2,c=3) #打印:<__main__.People object at 0x10076dd30> (1, 2, 3) {'a': 1, 'b': 2, 'c': 3} #總結:如果說類People是元類type的實例,那么在元類type內肯定也有一個__call__,會在調用People('egon',18)時觸發執行,然后返回一個初始化好了的對象obj #步驟三:自定義元類,控制類的調用(即實例化)的過程 class Mymeta(type): #繼承默認元類的一堆屬性 def __init__(self,class_name,class_bases,class_dic): if not class_name.istitle(): raise TypeError('類名首字母必須大寫') super(Mymeta,self).__init__(class_name,class_bases,class_dic) def __call__(self, *args, **kwargs): #self=People print(self,args,kwargs) #<class '__main__.People'> ('egon', 18) {} #1、實例化People,產生空對象obj obj=object.__new__(self) #2、調用People下的函數__init__,初始化obj self.__init__(obj,*args,**kwargs) #3、返回初始化好了的obj return obj class People(object,metaclass=Mymeta): country='China' def __init__(self,name,age): self.name=name self.age=age def talk(self): print('%s is talking' %self.name) obj=People('egon',18) print(obj.__dict__) #{'name': 'egon', 'age': 18} #步驟四: class Mymeta(type): #繼承默認元類的一堆屬性 def __init__(self,class_name,class_bases,class_dic): if not class_name.istitle(): raise TypeError('類名首字母必須大寫') super(Mymeta,self).__init__(class_name,class_bases,class_dic) def __call__(self, *args, **kwargs): #self=People print(self,args,kwargs) #<class '__main__.People'> ('egon', 18) {} #1、調用self,即People下的函數__new__,在該函數內完成:1、產生空對象obj 2、初始化 3、返回obj obj=self.__new__(self,*args,**kwargs) #2、一定記得返回obj,因為實例化People(...)取得就是__call__的返回值 return obj class People(object,metaclass=Mymeta): country='China' def __init__(self,name,age): self.name=name self.age=age def talk(self): print('%s is talking' %self.name) def __new__(cls, *args, **kwargs): obj=object.__new__(cls) cls.__init__(obj,*args,**kwargs) return obj obj=People('egon',18) print(obj.__dict__) #{'name': 'egon', 'age': 18} #步驟五:基於元類實現單例模式,比如數據庫對象,實例化時參數都一樣,就沒必要重復產生對象,浪費內存 class Mysql: __instance=None def __init__(self,host='127.0.0.1',port='3306'): self.host=host self.port=port @classmethod def singleton(cls,*args,**kwargs): if not cls.__instance: cls.__instance=cls(*args,**kwargs) return cls.__instance obj1=Mysql() obj2=Mysql() print(obj1 is obj2) #False obj3=Mysql.singleton() obj4=Mysql.singleton() print(obj3 is obj4) #True #應用:定制元類實現單例模式 class Mymeta(type): def __init__(self,name,bases,dic): #定義類Mysql時就觸發 self.__instance=None super().__init__(name,bases,dic) def __call__(self, *args, **kwargs): #Mysql(...)時觸發 if not self.__instance: self.__instance=object.__new__(self) #產生對象 self.__init__(self.__instance,*args,**kwargs) #初始化對象 #上述兩步可以合成下面一步 # self.__instance=super().__call__(*args,**kwargs) return self.__instance class Mysql(metaclass=Mymeta): def __init__(self,host='127.0.0.1',port='3306'): self.host=host self.port=port obj1=Mysql() obj2=Mysql() print(obj1 is obj2) 峰哥5步帶你學會元類
6 練習題
練習一:在元類中控制把自定義類的數據屬性都變成大寫

class Mymetaclass(type): def __new__(cls,name,bases,attrs): update_attrs={} for k,v in attrs.items(): if not callable(v) and not k.startswith('__'): update_attrs[k.upper()]=v else: update_attrs[k]=v return type.__new__(cls,name,bases,update_attrs) class Chinese(metaclass=Mymetaclass): country='China' tag='Legend of the Dragon' #龍的傳人 def walk(self): print('%s is walking' %self.name) print(Chinese.__dict__) ''' {'__module__': '__main__', 'COUNTRY': 'China', 'TAG': 'Legend of the Dragon', 'walk': <function Chinese.walk at 0x0000000001E7B950>, '__dict__': <attribute '__dict__' of 'Chinese' objects>, '__weakref__': <attribute '__weakref__' of 'Chinese' objects>, '__doc__': None} '''
練習二:在元類中控制自定義的類無需__init__方法
1.元類幫其完成創建對象,以及初始化操作;
2.要求實例化時傳參必須為關鍵字形式,否則拋出異常TypeError: must use keyword argument
3.key作為用戶自定義類產生對象的屬性,且所有屬性變成大寫

class Mymetaclass(type): # def __new__(cls,name,bases,attrs): # update_attrs={} # for k,v in attrs.items(): # if not callable(v) and not k.startswith('__'): # update_attrs[k.upper()]=v # else: # update_attrs[k]=v # return type.__new__(cls,name,bases,update_attrs) def __call__(self, *args, **kwargs): if args: raise TypeError('must use keyword argument for key function') obj = object.__new__(self) #創建對象,self為類Foo for k,v in kwargs.items(): obj.__dict__[k.upper()]=v return obj class Chinese(metaclass=Mymetaclass): country='China' tag='Legend of the Dragon' #龍的傳人 def walk(self): print('%s is walking' %self.name) p=Chinese(name='egon',age=18,sex='male') print(p.__dict__)