python入門學(xué)習(xí)筆記之元類

本節(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__)
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末只盹,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子兔院,更是在濱河造成了極大的恐慌殖卑,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件坊萝,死亡現(xiàn)場離奇詭異孵稽,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)十偶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門菩鲜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人惦积,你說我怎么就攤上這事接校。” “怎么了狮崩?”我有些...
    開封第一講書人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵蛛勉,是天一觀的道長。 經(jīng)常有香客問我睦柴,道長诽凌,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任坦敌,我火速辦了婚禮侣诵,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘狱窘。我一直安慰自己杜顺,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開白布训柴。 她就那樣靜靜地躺著哑舒,像睡著了一般。 火紅的嫁衣襯著肌膚如雪幻馁。 梳的紋絲不亂的頭發(fā)上洗鸵,一...
    開封第一講書人閱讀 49,036評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音仗嗦,去河邊找鬼膘滨。 笑死,一個(gè)胖子當(dāng)著我的面吹牛稀拐,可吹牛的內(nèi)容都是我干的火邓。 我是一名探鬼主播,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼铲咨!你這毒婦竟也來了躲胳?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤纤勒,失蹤者是張志新(化名)和其女友劉穎坯苹,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體摇天,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡粹湃,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了泉坐。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片为鳄。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖腕让,靈堂內(nèi)的尸體忽然破棺而出孤钦,到底是詐尸還是另有隱情,我是刑警寧澤记某,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布司训,位于F島的核電站,受9級(jí)特大地震影響液南,放射性物質(zhì)發(fā)生泄漏壳猜。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一滑凉、第九天 我趴在偏房一處隱蔽的房頂上張望统扳。 院中可真熱鬧,春花似錦畅姊、人聲如沸咒钟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽朱嘴。三九已至,卻和暖如春粗合,著一層夾襖步出監(jiān)牢的瞬間萍嬉,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國打工隙疚, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留壤追,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓供屉,卻偏偏與公主長得像行冰,于是被迫代替她去往敵國和親溺蕉。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容