本節(jié)重點(diǎn)
- 了解元類
- 了解元類的用途
本節(jié)時(shí)長需控制在45分鐘內(nèi)
一 知識(shí)儲(chǔ)備
exec:三個(gè)參數(shù)
參數(shù)一:字符串形式的命令
參數(shù)二:全局作用域(字典形式)礼华,如果不指定琢融,默認(rèn)為globals()
參數(shù)三:局部作用域(字典形式)耿眉,如果不指定,默認(rèn)為locals()
exec的使用
#可以把exec命令的執(zhí)行當(dāng)成是一個(gè)函數(shù)的執(zhí)行,會(huì)將執(zhí)行期間產(chǎn)生的名字存放于局部名稱空間中
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}
二 引子(類也是對(duì)象)
class Foo:
pass
f1=Foo() #f1是通過Foo類實(shí)例化的對(duì)象
python中一切皆是對(duì)象忽媒,類本身也是一個(gè)對(duì)象窒朋,當(dāng)使用關(guān)鍵字class的時(shí)候搀罢,python解釋器在加載class的時(shí)候就會(huì)創(chuàng)建一個(gè)對(duì)象(這里的對(duì)象指的是類而非類的實(shí)例),因而我們可以將類當(dāng)作一個(gè)對(duì)象去使用侥猩,同樣滿足第一類對(duì)象的概念榔至,可以:
把類賦值給一個(gè)變量
把類作為函數(shù)參數(shù)進(jìn)行傳遞
把類作為函數(shù)的返回值
在運(yùn)行時(shí)動(dòng)態(tài)地創(chuàng)建類
上例可以看出f1是由Foo這個(gè)類產(chǎn)生的對(duì)象,而Foo本身也是對(duì)象欺劳,那它又是由哪個(gè)類產(chǎn)生的呢唧取?
type函數(shù)可以查看類型,也可以用來查看對(duì)象的類划提,二者是一樣的
print(type(f1)) # 輸出:<class 'main.Foo'> 表示兵怯,obj 對(duì)象由Foo類創(chuàng)建
print(type(Foo)) # 輸出:<type 'type'>
三 什么是元類?
元類是類的類腔剂,是類的模板
元類是用來控制如何創(chuàng)建類的媒区,正如類是創(chuàng)建對(duì)象的模板一樣,而元類的主要目的是為了控制類的創(chuàng)建行為
元類的實(shí)例化的結(jié)果為我們用class定義的類掸犬,正如類的實(shí)例為對(duì)象(f1對(duì)象是Foo類的一個(gè)實(shí)例袜漩,F(xiàn)oo類是 type 類的一個(gè)實(shí)例)
type是python的一個(gè)內(nèi)建元類,用來直接控制生成類湾碎,python中任何class定義的類其實(shí)都是type類實(shí)例化的對(duì)象
![image_1c1p03hmh198t1skn1ulrps57h19.png-80.6kB][1]
四 創(chuàng)建類的兩種方式
方式一:使用class關(guān)鍵字
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)
方式二:就是手動(dòng)模擬class創(chuàng)建類的過程):將創(chuàng)建類的步驟拆分開宙攻,手動(dòng)去創(chuàng)建
#準(zhǔn)備工作:
#創(chuàng)建類主要分為三部分
1 類名
2 類的父類
3 類體
#類名
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)
"""
步驟一(先處理類體->名稱空間):類體定義的名字都會(huì)存放于類的名稱空間中(一個(gè)局部的名稱空間),我們可以事先定義一個(gè)空字典介褥,然后用exec去執(zhí)行類體的代碼(exec產(chǎn)生名稱空間的過程與真正的class過程類似座掘,只是后者會(huì)將__開頭的屬性變形),生成類的局部名稱空間柔滔,即填充字典
class_dic={}
exec(class_body,globals(),class_dic)
print(class_dic)
#{'country': 'China', 'talk': <function talk at 0x101a560c8>, '__init__': <function __init__ at 0x101a56668>}
步驟二:調(diào)用元類type(也可以自定義)來產(chǎn)生類Chinense
Foo=type(class_name,class_bases,class_dic) #實(shí)例化type得到對(duì)象Foo溢陪,即我們用class定義的類Foo
print(Foo)
print(type(Foo))
print(isinstance(Foo,type))
'''
<class '__main__.Chinese'>
<class 'type'>
True
'''
我們看到,type 接收三個(gè)參數(shù):
第 1 個(gè)參數(shù)是字符串 ‘Foo’睛廊,表示類名
第 2 個(gè)參數(shù)是元組 (object, )形真,表示所有的父類
第 3 個(gè)參數(shù)是字典,這里是一個(gè)空字典超全,表示沒有定義屬性和方法
補(bǔ)充:若Foo類有繼承咆霜,即class Foo(Bar):.... 則等同于type('Foo',(Bar,),{})
五 自定義元類控制類的行為
#一個(gè)類沒有聲明自己的元類邓馒,默認(rèn)他的元類就是type,除了使用元類type蛾坯,用戶也可以通過繼承type來自定義元類(順便我們也可以瞅一瞅元類如何控制類的行為光酣,工作流程是什么)
egon5步帶你學(xué)會(huì)元類
#!B隹巍救军!如果你拷貝不注明出處的話,以后老子都不寫了O卖帷g脱浴!
#知識(shí)儲(chǔ)備:
#產(chǎn)生的新對(duì)象 = object.__new__(繼承object類的子類)
#步驟一:如果說People=type(類名,類的父類們,類的名稱空間)视事,那么我們定義元類如下胆萧,來控制類的創(chuàng)建
class Mymeta(type): # 繼承默認(rèn)元類的一堆屬性
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)
#步驟二:如果我們想控制類實(shí)例化的行為,那么需要先儲(chǔ)備知識(shí)__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)
# 調(diào)用類People俐东,并不會(huì)出發(fā)__call__
obj=People('egon',18)
# 調(diào)用對(duì)象obj(1,2,3,a=1,b=2,c=3)跌穗,才會(huì)出發(fā)對(duì)象的綁定方法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}
#總結(jié):如果說類People是元類type的實(shí)例虏辫,那么在元類type內(nèi)肯定也有一個(gè)__call__蚌吸,會(huì)在調(diào)用People('egon',18)時(shí)觸發(fā)執(zhí)行,然后返回一個(gè)初始化好了的對(duì)象obj
#步驟三:自定義元類砌庄,控制類的調(diào)用(即實(shí)例化)的過程
class Mymeta(type): #繼承默認(rèn)元類的一堆屬性
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羹唠、實(shí)例化People,產(chǎn)生空對(duì)象obj
obj=object.__new__(self)
#2娄昆、調(diào)用People下的函數(shù)__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): #繼承默認(rèn)元類的一堆屬性
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萌焰、調(diào)用self哺眯,即People下的函數(shù)__new__,在該函數(shù)內(nèi)完成:1扒俯、產(chǎn)生空對(duì)象obj 2奶卓、初始化 3、返回obj
obj=self.__new__(self,*args,**kwargs)
#2撼玄、一定記得返回obj夺姑,因?yàn)閷?shí)例化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}
#步驟五:基于元類實(shí)現(xiàn)單例模式,比如數(shù)據(jù)庫對(duì)象,實(shí)例化時(shí)參數(shù)都一樣,就沒必要重復(fù)產(chǎn)生對(duì)象,浪費(fèi)內(nèi)存
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
#應(yīng)用:定制元類實(shí)現(xiàn)單例模式
class Mymeta(type):
def __init__(self,name,bases,dic): #定義類Mysql時(shí)就觸發(fā)
self.__instance=None
super().__init__(name,bases,dic)
def __call__(self, *args, **kwargs): #Mysql(...)時(shí)觸發(fā)
if not self.__instance:
self.__instance=object.__new__(self) #產(chǎn)生對(duì)象
self.__init__(self.__instance,*args,**kwargs) #初始化對(duì)象
#上述兩步可以合成下面一步
# 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)
六 練習(xí)題
練習(xí)一:在元類中控制把自定義類的數(shù)據(jù)屬性都變成大寫
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}
'''
練習(xí)二:在元類中控制自定義的類無需init方法
1.元類幫其完成創(chuàng)建對(duì)象,以及初始化操作互纯;
2.要求實(shí)例化時(shí)傳參必須為關(guān)鍵字形式瑟幕,否則拋出異常TypeError: must use keyword argument
3.key作為用戶自定義類產(chǎn)生對(duì)象的屬性,且所有屬性變成大寫
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) #創(chuàng)建對(duì)象留潦,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__)