Python高級語法-貫徹回顧-元類(4.99.1)

@java

1.爲何要掌握元類

在django中編寫models的時候遇到了元類的相關操做
而且在mini-web框架編寫的時候也遇到了相關的問題
意識到深刻理解元類很是的重要因此補票重學python

學習且引用來自博客:https://www.cnblogs.com/intimacy/p/8119449.htmlgit

2.正文

python中一切都是對象,類也是對象
那麼意味着,類能夠執行如下操做github

  • 1)將他賦值給一個變量
  • 2)拷貝他
  • 3)給他設置屬性
  • 4)將他當作方法的參數傳遞  
>>> print(ObjectCreator) # 打印此對象
<class '__main__.ObjectCreator'>
>>> def echo(o):
...       print(o)
...
>>> echo(ObjectCreator) # 將此對象當作方法的參數
<class '__main__.ObjectCreator'>
>>> print(hasattr(ObjectCreator, 'new_attribute'))
False
>>> ObjectCreator.new_attribute = 'foo' # 給此對象添加屬性
>>> print(hasattr(ObjectCreator, 'new_attribute'))
True
>>> print(ObjectCreator.new_attribute)
foo
>>> ObjectCreatorMirror = ObjectCreator # 將此對象賦值給一個變量
>>> print(ObjectCreatorMirror.new_attribute)
foo
>>> print(ObjectCreatorMirror())
<__main__.ObjectCreator object at 0x8997b4c>

由於類是對象,那麼意味着能夠動態的生成
粗略的覺得,在java等語言中,類使用來定義一個對象的,也就是用來定義起亞數據類型的,私覺得不能夠更改。可是python好像就能夠更改類的源代碼,致使你想在運行的時候從新規定一下類中的屬性和方法也是能夠的,好比下面的代碼傳入foo參數的時候在內存中定義一個class對象,可是傳入其餘的時候就不定義web

>>> def choose_class(name):
...     if name == 'foo':
...         class Foo(object):
...             pass
...         return Foo # 返回Foo類,而不是對象
...     else:
...         class Bar(object):
...             pass
...         return Bar
...
>>> MyClass = choose_class('foo')
>>> print(MyClass) # 方法返回類,不返回對象
<class '__main__.Foo'>
>>> print(MyClass()) # 能夠經過方法返回的類建立對象
<__main__.Foo object at 0x89c6d4c>

但這彷佛不是動態建立的,由於咱們寫了一段代碼來生成他,而後python解釋器遇到class關鍵字的時候就自動給咱們程序建立了一個類對象,那麼如何本身建立一個,使用type數據庫

>>> print(type(1))
<type 'int'>
>>> print(type("1"))
<type 'str'>
>>> print(type(ObjectCreator))
<type 'type'>
>>> print(type(ObjectCreator()))
<class '__main__.ObjectCreator'>

type在咱們學習python的歷程中能夠知道是用來判斷數據類型的,可是type確實能夠用來建立一個類對象,格式以下django

type(類對象的名稱, 元組形式的全部父類對象 (繼承用,能夠爲空),包含屬性名和屬性值的字典)編程

>>> class MyShinyClass(object):
...       pass
-------------------------------------------------------------------
>>> MyShinyClass = type('MyShinyClass', (), {}) # 返回一個類對象
>>> print(MyShinyClass)
<class '__main__.MyShinyClass'>
>>> print(MyShinyClass()) # 經過返回的類對象建立一個對象
<__main__.MyShinyClass object at 0x8997cec>

上面兩部分代碼實現的功能是同樣的
再如框架

>>> class Foo(object):
...       bar = True
--------------------------------------------------------------
>>> Foo = type('Foo', (), {'bar':True})


#能夠向正常的類同樣使用
>>> print(Foo)
<class '__main__.Foo'>
>>> print(Foo.bar)
True
>>> f = Foo()
>>> print(f)
<__main__.Foo object at 0x8a9b84c>
>>> print(f.bar)
True

這兩部分代碼的功能也是同樣的,因此讓咱們再次熟悉一下type建立類對象的使用方法,第一個參數爲類的名字,第二個參數爲多繼承使用的元組,第三個參數爲一個列表,用來定義類中的方法和屬性

>>> def echo_bar(self):
...       print(self.bar)
...
>>> FooChild = type('FooChild', (Foo,), {'echo_bar': echo_bar})
>>> hasattr(Foo, 'echo_bar')
False
>>> hasattr(FooChild, 'echo_bar')
True
>>> my_foo = FooChild()
>>> my_foo.echo_bar()
True

定義方法的時候如上,固然能夠再定義了這個類的時候往裏面加其餘的方法

>>> def echo_bar_more(self):
...       print('yet another method')
...
>>> FooChild.echo_bar_more = echo_bar_more
>>> hasattr(FooChild, 'echo_bar_more')
True

通過上述能夠知道在python中,類就是對象,你能在運行中動態的建立類

那麼metaclass是啥呢
metaclass是建立class的東西,定義類的目的,就是爲了建立一個對象,可是咱們已經知道了,python中類就是一個對象,那麼metaclass就是建立這種對象(類)的東西,他們是用來描述類的,能夠理解爲

MyClass = MetaClass()
MyObject = MyClass()
#使用metaclass建立了一個類對象,在使用這個類對象去建立其餘的對象

咱們已經知道,type能夠爲咱們建立一個對象
是由於方法type其實是一個metaclass,type就是python用來建立其餘類的metaclass, 你能夠經過__class__屬性來驗證這一點 也就是說type就是用來建立其餘class的class,在Python中一切皆對象,一切皆對象,包括int str function(方法) class(類),全部一切都是對象,並且他們都是有某一個類建立

>>> age = 35
>>> age.__class__
<type 'int'>
>>> name = 'bob'
>>> name.__class__
<type 'str'>
>>> def foo(): pass
>>> foo.__class__
<type 'function'>
>>> class Bar(object): pass
>>> b = Bar()
>>> b.__class__
<class '__main__.Bar'>

能夠看到全部的數據類型,都是從對象中建立獲得的

那麼class的class是啥呢

>>> age.__class__.__class__
<type 'type'>
>>> name.__class__.__class__
<type 'type'>
>>> foo.__class__.__class__
<type 'type'>
>>> b.__class__.__class__
<type 'type'>
因此一個metaclass就是用來建立類對象的東西,你能夠稱之爲「類工廠」。而type就是Python內建的metaclass(類工廠),固然你能定義你本身的metaclass

在python中建立一個對象以前,回去找metaclass,若是沒有指定的話就使用type來建立類對象

Python會:
    一、在Foo中有沒有指定__metaclass__屬性
    二、若是找到,就會經過__metaclass__指定的東西建立Foo類對象(注意此處是類對象)
    三、若是找不到,就會去MODULE(模塊)級別去找,並建立Foo類對象(但只適用於沒有繼承任何父類的類,基本上就是老式類)
    四、若是都找不到,就會經過Bar(第一個父類)的metaclass(頗有多是type)來建立Foo類對象,
    五、這裏要注意__metaclass__不會被繼承,而父類的__class__(Bar.__class__)會被子類繼承.即:若是Bar用了__metaclass__屬性經過type()而不是type.__new__()建立Bar類對象,那麼子類不會繼承這種行爲。
如今問題是:能夠將什麼指定給__metaclass__。
答案是:能建立類的東西。
什麼能建立類呢?type type的子類 和全部使用type的

自定義metaclass

metaclass的主要做用就是建立類時使類發生變化
一般作API時纔會用到,就是建立和上下文相吻合的類(這句翻的太爛)。
舉個例子:你決定全部模塊下的類的屬性都要大寫,有不少種方法實現此需求,其中一種就是在module級別設置__metaclass__屬性。
這樣一來,此模塊下全部類都經過指定的metaclass建立,你只須要告訴metaclass將全部屬性大寫便可。
幸運的是__metaclass__只要可被調用便可,它沒必要是一個類。
因此咱們先舉個例子,用一個方法作metaclass
# the metaclass will automatically get passed the same argument that you usually pass to `type`
def upper_attr(future_class_name, future_class_parents, future_class_attr):
   """
       Return a class object, with the list of its attribute turned
       into uppercase.
   """

   # pick up any attribute that doesn't start with '__' and uppercase it
   uppercase_attr = {}
   for name, val in future_class_attr.items():
           if not name.startswith('__'):
                   uppercase_attr[name.upper()] = val
           else:
                   uppercase_attr[name] = val

   # let `type` do the class creation
   return type(future_class_name, future_class_parents, uppercase_attr)

__metaclass__ = upper_attr # this will affect all classes in the module

class Foo(): # global __metaclass__ won't work with "object" though
   # but we can define __metaclass__ here instead to affect only this class
   # and this will work with "object" children
   bar = 'bip'

print(hasattr(Foo, 'bar'))
# Out: False
print(hasattr(Foo, 'BAR'))
# Out: True

f = Foo()
print(f.BAR)
# Out: 'bip'

如今使用一個類作metaclass來實現
``python # remember thattypeis actually a class likestrandint`

so you can inherit from it

class UpperAttrMetaclass(type):
# new is the method called before init
# it's the method that creates the object and returns it
# while init just initializes the object passed as parameter
# you rarely use new, except when you want to control how the object
# is created.
# here the created object is the class, and we want to customize it
# so we override new
# you can do some stuff in init too if you wish
# some advanced use involves overriding call as well, but we won't
# see this
def new(upperattr_metaclass, future_class_name,
future_class_parents, future_class_attr):

uppercase_attr = {}
    for name, val in future_class_attr.items():
        if not name.startswith('__'):
            uppercase_attr[name.upper()] = val
        else:
            uppercase_attr[name] = val

    return type(future_class_name, future_class_parents, uppercase_attr)

```
上面代碼說的new方法是在init方法以前調用的,new方法是建立一個對象並返回他,init方法只是經過傳入參數初始化一個對象,咱們幾乎用不到new方法,若是你不是想去控制類對象如何生成

上面的方式不是很"面向對象",由於咱們直接調用了type方法,並且沒有重寫也沒有調用父類的__new__方法,下面這種方式就好一些

**class UpperAttrMetaclass(type):
    def __new__(upperattr_metaclass, future_class_name,
                future_class_parents, future_class_attr):

        uppercase_attr = {}
        for name, val in future_class_attr.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val

        # reuse the type.__new__ method
        # this is basic OOP, nothing magic in there
        return type.__new__(upperattr_metaclass, future_class_name,
                            future_class_parents, uppercase_attr)**
你可能已經注意到了參數"upperattr_metaclass",這個參數並無什麼特殊的,__new__方法老是以所在類中的類對象做爲第一個參數,就像普通方法中的self參數同樣,將實例做爲第一個參數。
固然我用的名字都見名知意,但就像self,全部的參數也有簡短的名字,因此一個真正的metaclass會像下面這樣:
class UpperAttrMetaclass(type):
    def __new__(cls, clsname, bases, dct):

        uppercase_attr = {}
        for name, val in dct.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val

        return type.__new__(cls, clsname, bases, uppercase_attr)
咱們能夠經過使用super關鍵字使類更清晰:
class UpperAttrMetaclass(type):
    def __new__(cls, clsname, bases, dct):

        uppercase_attr = {}
        for name, val in dct.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val

        return super(UpperAttrMetaclass, cls).__new__(cls, clsname, bases, uppercase_attr)
實際上,metaclass在作黑魔法時尤爲 有用,也就是作複雜的事情。至於metaclass自己,仍是很簡單的:
    一、攔截類的生成
    二、修改類
    三、返回修改後的類

爲何用metaclass而不用方法呢?
既然__metaclass__能接收一切可被調用的東西,爲何要用class呢,class但是比方法複雜的多啊!!!
主要是如下幾個緣由:
一、目的更明確。你看到UpperAttrMetaclass(type),就知道是什麼意思
二、能用"面向對象編程",metaclass能夠繼承metaclass能夠重寫父類方法,甚至可使用其餘metaclass。
三、某個類若是你指定了metaclass,則它的子類是這個metaclass的實例,但若是指定了metaclass爲function則不是(一個類不能是某個方法的實例)。
四、代碼結構會更好。上面使用metaclass的例子已經很是簡單了,經過使用metaclass的狀況都是比較複雜的。將全部方法組織在一個class中,能使你的代碼更易讀。
五、能夠在__new__,__init__和__call__中作你想作的事,雖然你能都在__new__寫,但有些人仍是習慣在__init_中實現。
六、叫metaclass,該死的,這名字仍是有些意義的。

  • metaclass的主要用於建立API,典型的例子就是Django ORM。它容許你這樣定義:
class Person(models.Model):
    name = models.CharField(max_length=30)
    age = models.IntegerField()

但若是你這樣作

guy = Person(name='bob', age='35')
print(guy.age)
代碼不返回IntegerField對象,而返回int,甚至是直接在數據庫中取出的數據。
    這多是由於,models.Model定義了__metaclass__,而指向的metaclass將你定義的簡單的Person類變爲數據庫中的一個字段。
    利用metaclass,Django暴露一套API使得複雜的事情看起來很簡單,而在幕後作真正的工做。

最後:
首先,類就是能建立實例的對象。實際上,類是metaclass的實例

>>> class Foo(object): pass
>>> id(Foo)
142630324
在Python中一切都是對象(實例),他們不是類的實例就是metaclass的實例。
除了type。type的metaclass是本身,實際上在純Python中是不能建立type的,而是經過在Python的實現層做弊實現的。
其次,metaclass是很複雜的。若是你只要對類作一點點調整,就不要使用metaclass,你能夠用另外兩種技術來實現:
    猴子補丁(monkey patching)
    裝飾器
當你須要對類作調整,99%的狀況下你能夠經過以上兩種方式實現。
可是98%的狀況是:你徹底不須要對類作調整

關於做者

我的博客網站
我的GitHub地址
我的公衆號:
在這裏插入圖片描述

相關文章
相關標籤/搜索