Python 元類

前提

newinitcall的介紹

在講到使用元類創建單例模式之前,比需了解new這個內置方法的作用,在上面講元類的時候我們用到了new方法來實現類的創建。然而我在那之前還是對new這個方法和init方法有一定的疑惑。因此這里花點時間對其概念做一次了解和區分。

new

該方法負責創建一個實例對象,在對象被創建的時候調用該方法它是一個類方法。new方法在返回一個實例之后,會自動的調用init方法,對實例進行初始化。如果new方法不返回值,或者返回的不是實例,那么它就不會自動的去調用init方法。

init

該方法負責將該實例對象進行初始化,在對象被創建之后調用該方法,在new方法創建出一個實例后對實例屬性進行初始化。init方法可以沒有返回值。

call

該方法其實和類的創建過程和實例化沒有多大關系了,定義了call方法才能被使用函數的方式執行。

#coding:utf-8
class Foo(object):
    def __new__(cls, *args, **kwargs):
        #__new__是一個類方法,在對象創建的時候調用
        print "excute __new__"
        return super(Foo,cls).__new__(cls,*args,**kwargs)


    def __init__(self,value):
        #__init__是一個實例方法,在對象創建后調用,對實例屬性做初始化
        print "excute __init"
        self.value = value


f1 = Foo(1)
print f1.value
f2 = Foo(2)
print f2.value
#輸出===:
excute __new__
excute __init
excute __new__
excute __init
#====可以看出new方法在init方法之前執行

type

type(類名, 父類的元組(針對繼承的情況,可以為空),包含屬性的字典(名稱和值))

元類的作用是產生類實例

def choose_class(name):
    if name == 'foo':
        class Foo(object):
            pass
        return Foo     # 返回的是類,不是類的實例
    else:
        class Bar(object):
            pass
        return Bar
MyClass = choose_class('foo')

print MyClass              # 函數返回的是類,不是類的實例
#輸出:<class '__main__.Foo'>

print MyClass()            # 你可以通過這個類創建類實例,也就是對象
#輸出:<__main__.Foo object at 0x1085ed950

在Python中,類也是對象,你可以動態的創建類。這就是當我們使用關鍵字class時Python在幕后做的事情,而這就是通過元類來實現的

元類

什么是元類

通過上文的描述,我們知道了Python中的類也是對象。元類就是用來創建這些類(對象)的,元類就是類的類,你可以這樣理解為:

MyClass = MetaClass()    #元類創建
MyObject = MyClass()     #類創建實例
實際上MyClass就是通過type()來創創建出MyClass類,它是type()類的一個實例;同時MyClass本身也是類,也可以創建出自己的實例,這里就是MyObject

metaclass屬性

你可以在寫一個類的時候為其添加metaclass屬性,定義了metaclass就定義了這個類的元類。

class Foo(object):   #py2
    __metaclass__ = something…


class Foo(metaclass=something):   #py3
    __metaclass__ = something…

例如:當我們寫如下代碼時 :

class Foo(Bar):
    pass

在該類并定義的時候,它還沒有在內存中生成,知道它被調用。Python做了如下的操作:
1)Foo中有metaclass這個屬性嗎?如果是,Python會在內存中通過metaclass創建一個名字為Foo的類對象(我說的是類對象,請緊跟我的思路)。
2)如果Python沒有找到metaclass,它會繼續在父類中尋找metaclass屬性,并嘗試做和前面同樣的操作。
3)如果Python在任何父類中都找不到metaclass,它就會在模塊層次中去尋找metaclass,并嘗試做同樣的操作。
4)如果還是找不到metaclass,Python就會用內置的type來創建這個類對象。

現在的問題就是,你可以在metaclass中放置些什么代碼呢?
答案就是:可以創建一個類的東西。那么什么可以用來創建一個類呢?type,或者任何使用到type或者子類化type的東西都可以。

使用函數當做元類

def upper_attr(future_class_name, future_class_parents, future_class_attr):
    '''返回一個類對象,將屬性都轉為大寫形式'''
    #選擇所有不以'__'開頭的屬性
    attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith('__'))
    # 將它們轉為大寫形式
    uppercase_attr = dict((name.upper(), value) for name, value in attrs)
    #通過'type'來做類對象的創建
    return type(future_class_name, future_class_parents, uppercase_attr)#返回一個類

class Foo(object):
    __metaclass__ = upper_attr
    bar = 'bip' 

使用class來當做元類

class UpperAttrMetaclass(type):
    def __new__(cls, name, bases, dct):
        attrs = ((name, value) for name, value in dct.items() if not name.startswith('__'))
        uppercase_attr = dict((name.upper(), value) for name, value in attrs)
        return super(UpperAttrMetaclass, cls).__new__(cls, name, bases, uppercase_attr)

orm

#coding:utf-8
#一、首先來定義Field類,它負責保存數據庫表的字段名和字段類型:
class Field(object):
    def __init__(self, name, column_type):
        self.name = name
        self.column_type = column_type
    def __str__(self):
        return '<%s:%s>' % (self.__class__.__name__, self.name)

class StringField(Field):
    def __init__(self, name):
        super(StringField, self).__init__(name, 'varchar(100)')

class IntegerField(Field):
    def __init__(self, name):
        super(IntegerField, self).__init__(name, 'bigint')

#二、定義元類,控制Model對象的創建
class ModelMetaclass(type):
    '''定義元類, __new__必須要有返回值,返回實例化出來的實例,此處返回Model類實例'''
    def __new__(cls, name, bases, attrs):
        if name=='Model':
            # 實例化出<class '__main__.Model'>
            # super(ModelMetaclass,cls) 等于type
            '''
            __new__()方法接收到的參數依次是:
            1、當前準備創建的類的對象;
            2、類的名字;
            3、類繼承的父類集合;
            4、類的方法或者屬性集合。
            '''
            cc = super(ModelMetaclass,cls).__new__(cls, name, bases, attrs)
            return cc
        mappings = dict()
        for k, v in attrs.iteritems():
            # 保存類屬性和列的映射關系到mappings字典
            if isinstance(v, Field):
                print('Found mapping: %s==>%s' % (k, v))
                mappings[k] = v
        for k in mappings.iterkeys():
            #將類屬性移除,使定義的類字段不污染User類屬性,只在實例中可以訪問這些key
            attrs.pop(k)
        attrs['__table__'] = name.lower() # 假設表名和為類名的小寫,創建類時添加一個__table__類屬性
        attrs['__mappings__'] = mappings # 保存屬性和列的映射關系,創建類時添加一個__mappings__類屬性
        dd = super(ModelMetaclass,cls).__new__(cls, name, bases, attrs)
        return dd

#三、編寫Model基類
class Model(dict):
    __metaclass__ = ModelMetaclass

    def __init__(self, **kw):
        super(Model, self).__init__(**kw)

    def __getattr__(self, key):
        try:
            return self[key]
        except KeyError:
            raise AttributeError(r"'Model' object has no attribute '%s'" % key)

    def __setattr__(self, key, value):
        self[key] = value

    def save(self):
        fields = []
        params = []
        args = []
        for k, v in self.__mappings__.iteritems():
            fields.append(v.name)
            params.append('?')
            args.append(getattr(self, k, None))
        sql = 'insert into %s (%s) values (%s)' % (self.__table__, ','.join(fields), ','.join(params))
        print('SQL: %s' % sql)
        print('ARGS: %s' % str(args))

#最后,我們使用定義好的ORM接口,使用起來非常的簡單。
class User(Model):
    # 定義類的屬性到列的映射:
    id = IntegerField('id')
    name = StringField('username')
    email = StringField('email')
    password = StringField('password')

# 創建一個實例:
u = User(id=12345, name='Michael', email='test@orm.org', password='my-pwd')
# 保存到數據庫:
u.save()

元類是生成類的類 所以都放在初始化new函數中,讓生成類更加靈活。

元類單例模式

new方法實現單例

class Singleton(object):
    def __new__(cls, *args, **kwargs):
        if not hasattr(cls,"_instance"):
            cls._instance = super(Singleton, cls).__new__(cls, *args, **kwargs)
        return cls._instance


s1 = Singleton()
s2 = Singleton()

print s1 is s2

元類實現單例

class Singleton(type):
    def __init__(self, *args, **kwargs):
        print "__init__"
        self.__instance = None
        super(Singleton,self).__init__(*args, **kwargs)

    def __call__(self, *args, **kwargs):
        print "__call__"
        if self.__instance is None:
            self.__instance = super(Singleton,self).__call__(*args, **kwargs)
        return self.__instance


class Foo(object):
    __metaclass__ = Singleton #在代碼執行到這里的時候,元類中的__new__方法和__init__方法其實已經被執行了,而不是在Foo實例化的時候執行。且僅會執行一次。


foo1 = Foo()
foo2 = Foo()
print Foo.__dict__  #_Singleton__instance': <__main__.Foo object at 0x100c52f10> 存在一個私有屬性來保存屬性,而不會污染Foo類(其實還是會污染,只是無法直接通過__instance屬性訪問)

print foo1 is foo2  # True
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,321評論 6 543
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,559評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,442評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,835評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,581評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,922評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,931評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,096評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,639評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,374評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,591評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,104評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,789評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,196評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,524評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,322評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,554評論 2 379

推薦閱讀更多精彩內容