<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Python進(jìn)階——元類是怎么創(chuàng)建一個類的?

          共 9707字,需瀏覽 20分鐘

           ·

          2021-03-27 09:39

          如果你看過比較優(yōu)秀的 Python 開源框架,肯定見到過元類的身影。例如,在一個類中定義了類屬性 __metaclass__,這就說明這個類使用了元類來創(chuàng)建。
          那元類的實現(xiàn)原理究竟是怎樣的?使用元類能幫我們在開發(fā)中解決什么樣的問題?
          這篇文章,我們就來看一下 Python 元類的來龍去脈。

          什么是元類?

          我們都知道,定義一個類,然后調(diào)用它的構(gòu)造方法,就可以初始化出一個實例出來,就像下面這樣:
          class Person(object)

              def __init__(name):

                  self.name = name

          p = Person('zhangsan')
          那你有沒有想過,我們平時定義的類,它是如何創(chuàng)建出來的?
          別著急,我們先來看一個例子:
          >>> a = 1               # 創(chuàng)建a的類是int a是int的實例
          >>> a.__class__
          <type 'int'>

          >>> b = 'abc'           # 創(chuàng)建b的類是str b是str的實例
          >>> b.__class__
          <type 'str'>

          >>> def c():            # 創(chuàng)建c的類是function 方法c是function的實例
          ...     pass
          >>> c.__class__
          <type 'function'>

          >>> class D(object):    # 創(chuàng)建d的類是D d是D的實例
          ...     pass
          >>> d.__class__
          <class '__main__.D'>
          在這個例子中,我們定義了 intstrfunctionclass,然后分別調(diào)用了它們的__class__ 方法,這個 __class__ 方法可以返回實例是如何創(chuàng)建出來的。
          從方法返回的結(jié)果我們可以看到:
          • 創(chuàng)建整數(shù) a 的類是 int,也就是說 a 是 int 的一個實例
          • 創(chuàng)建字符串 b 的類是 str,也就是說 b 是 str 的一個實例
          • 創(chuàng)建函數(shù) c 的類是 function,也就是說 c 是 function 的一個實例
          • 創(chuàng)建實例 d 的類是 class,也就是說 d 是 class 的一個實例
          除了這些之外,我們在開發(fā)中使用到的例如 listdict 也類似,你可以測試觀察一下結(jié)果。
          現(xiàn)在我們已經(jīng)得知,創(chuàng)建這些實例的類是 intstrfunctionclass,那進(jìn)一步思考一下,這些類又是怎么創(chuàng)建出來的呢?
          同樣地,我們也調(diào)用這些類的 __class__ 方法,觀察結(jié)果:
          >>> a = 1
          >>> a.__class__.__class__
          <type 'type'>
          >>>
          >>> b = 'abc'
          >>> b.__class__.__class__
          <type 'type'>
          >>>
          >>> def c():
          ...     pass
          >>> c.__class__.__class__
          <type 'type'>
          >>>
          >>> class D(object):
          ...     pass
          >>> d = D()
          >>> d.__class__.__class__
          <type 'type'>
          從結(jié)果我們可以看到,創(chuàng)建這些類的類,都是 type,所以 type 就是創(chuàng)建所有類的「元類」。也就是說,元類的作用就是用來創(chuàng)建類的
          你可以這樣理解:
          1. 元類 -> 類
          2. 類 -> 實例
          用偽代碼表示,就是下面這樣:
          klass = MetaClass()     # 元類創(chuàng)建類
          obj = klass()           # 類創(chuàng)建實例
          是不是很有意思?
          在這里,你也可以感受一下這句話的含義:Python 中一切皆對象!
          無論是普通類型、方法、實例,還是類,都可以統(tǒng)一看作對象,它們的起源就是元類。
          其實,在 Python 中,使用 type 方法,我們可就以創(chuàng)建出一個類,type 方法的語法如下:
          type(class_name, (base_class, ...), {attr_key: attr_value, ...})
          例如,像下面這樣,我們使用 type 方法創(chuàng)建 MyClass 類,并且讓它繼承 object
          >>> A = type('MyClass', (object, ), {}) # type創(chuàng)建一個類,繼承object
          >>> A
          <class '__main__.MyClass'>
          >>> A()
          <__main__.MyClass object at 0x10d905950>
          我們還可以使用 type 創(chuàng)建一個包含屬性和方法的類:
          >>> def foo(self):
          ...     return 'foo'
          ...
          >>> name = 'zhangsan'
          >>>
          # type 創(chuàng)建類B 繼承object 包含 name 屬性和 foo 方法
          >>> B = type('MyClass', (object, ), {'name': name, 'foo': foo}) 
          >>> B.name          # 打印 name 屬性
          'zhangsan'
          >>> print B().foo() # 調(diào)用 foo 方法
          foo
          通過 type 方法創(chuàng)建的類,和我們自己定義一個類,在使用上沒有任何區(qū)別。
          其實,除了使用 type 方法創(chuàng)建一個類之外,我們還可以使用類屬性 __metaclass__ 創(chuàng)建一個類,這就是下面要講的「自定義元類」。

          自定義元類

          我們可以使用類屬性 __metaclass__ 把一個類的創(chuàng)建過程,轉(zhuǎn)交給其它地方,可以像下面這樣寫:
          class A(object):
              __metaclass__ = ... # 這個類的創(chuàng)建轉(zhuǎn)交給其他地方
              pass
          這個例子中,我們先定義了類 A,然后定義了一個類屬性 __metaclass__,這個屬性表示創(chuàng)建類 A 的過程,轉(zhuǎn)交給其它地方處理。
          那么,這個類屬性 __metaclass__ 需要怎么寫呢?
          其實,它可以是一個方法,也可以是一個類。

          用方法創(chuàng)建類

          如果類屬性 __metaclass__ 賦值的是一個方法,那么創(chuàng)建類的過程,就交給了一個方法來執(zhí)行。
          def create_class(name, bases, attr):
              print 'create class by method...'
              # 什么事都沒做 直接用type創(chuàng)建了一個類
              return type(name, bases, attr)

          class A(object):
              # 創(chuàng)建類的過程交給了一個方法
              __metaclass__ = create_class

          # Output:    
          # create class by method ...
          我們定義了 create_class 方法,然后賦值給 __metaclass__,那么類 A 被創(chuàng)建時,就會調(diào)用 create_class 方法。
          而 create_class 方法中的邏輯,就是我們上面所講到的,使用 type 方法創(chuàng)建出一個類,然后返回。

          用類創(chuàng)建類

          明白了用方法創(chuàng)建類之后,我們來看一下用類來創(chuàng)建另一個類。
          class B(type):
              # 必須定義 __new__ 方法 返回一個類
              def __new__(cls, name, bases, attr):
                  print 'create class by B ...'
                  return type(name, bases, attr)

          class A(object):
              # 創(chuàng)建類的過程交給了B
              __metaclass__ = B
              
          # Output:
          # create class by B ...
          在這個例子中,我們定義了類 B,然后把它賦值給了 A 的類變量 __metaclass__,這就表示創(chuàng)建 A 的過程,交給了類 B。
          B 在定義時,首先繼承了 type,然后定義了 __new__ 方法,最后調(diào)用 type 方法返回了一個類,這樣當(dāng)創(chuàng)建類 A 時,會自動調(diào)用類 B 的 __new__ 方法,然后得到一個類實例。

          創(chuàng)建類的過程

          好了,上面我們演示了通過元類創(chuàng)建一個類的兩種方式,分別是通過方法創(chuàng)建和通過類創(chuàng)建。
          其實創(chuàng)建一個類的完整流程如下:
          1. 檢查類中是否有 __metaclass__ 屬性,如果有,則調(diào)用 __metaclass__ 指定的方法或類創(chuàng)建
          2. 如果類中沒有 __metaclass__ 屬性,那么會繼續(xù)在父類中尋找
          3. 如果任何父類中都沒有,那么就用 type 創(chuàng)建這個類
          也就是說,如果我們沒有指定 __metaclass__,那么所有的類都是默認(rèn)由 type 創(chuàng)建,這種情況是我們大多數(shù)定義類時的流程。
          如果類中指定了 __metaclass__,那么這個類的創(chuàng)建就會交給外部來做,外部可以定義具體的創(chuàng)建邏輯。

          哪種創(chuàng)建類的方式更好?

          雖然有兩種方式可以創(chuàng)建類,那么哪種方式更好呢?
          一般我們建議使用類的方式創(chuàng)建,它的優(yōu)點如下:
          • 使用類更能清楚地表達(dá)意圖
          • 使用類更加 OOP,因為類可以繼承其他類,而且可以更友好地使用面向?qū)ο筇匦?/section>
          • 使用類可以更好地組織代碼結(jié)構(gòu)
          另外,使用類創(chuàng)建一個類時,這里有一個優(yōu)化點:在 __new__ 方法中不建議直接調(diào)用 type 方法,而是建議調(diào)用 super 的 __new__ 來創(chuàng)建類,執(zhí)行結(jié)果與 type 方法是一樣的:
          class B(type):

              def __new__(cls, name, bases, attr):
                  # 使用 super.__new__ 創(chuàng)建類
                  return super(B, cls).__new__(cls, name, bases, attr)    

          創(chuàng)建類時自定義行為

          前面我們用元類創(chuàng)建一個類時,它的功能非常簡單。現(xiàn)在我們來看一下,使用元類創(chuàng)建類時,如何定義一些自己的邏輯,然后改變類的屬性或行為。
          我們看下面這個例子:
          # coding: utf8

          class Meta(type):
              def __new__(cls, name, bases, attr):
                  # 通過 Meta 創(chuàng)建的類 屬性會都變成大寫
                  for k, v in attr.items():
                      if not k.startswith('__'):
                          attr[k] = v.upper()
                      else:
                          attr[k] = v
                  return type(name, bases, attr)

          class A(object):
              # 通過 Meta 創(chuàng)建類
              __metaclass__ = Meta

              name = 'zhangsan'

          class B(object):
              # 通過 Meta 創(chuàng)建類
              __metaclass__ = Meta

              name = 'lisi'

          # 打印類屬性 會自動變成大寫
          print A.name    # ZHANGSAN
          print B.name    # LISI
          在這個例子中,我們定義了一個元類 Meta,然后在定義類 A 和 B 時,把創(chuàng)建類的過程交給了  Meta,在 Meta 類中,我們可以拿到 A 和 B 的屬性,然后把它們的屬性都轉(zhuǎn)換成了大寫。
          所以當(dāng)我們打印 A 和 B 的屬性時,雖然定義的變量是小寫的,但輸出結(jié)果都變成了大寫,這就是元類發(fā)揮的作用。

          使用場景

          了解了元類的實現(xiàn)原理,那么元類都會用在哪些場景呢?
          我們在開發(fā)中其實用的并不多,元類的使用,經(jīng)常會出現(xiàn)在一些框架中,例如Django ORMpeewee,下面是使用 Django ORM 定義一個數(shù)據(jù)表映射類的代碼:
          class Person(models.Model):
              # 注意: name 和 age 是類屬性
              name = models.CharField(max_length=30)
              age = models.IntegerField()
              
          person = Person(name='zhangsan', age=20)
          print person.name   # zhangsan
          print person.age    # 20
          仔細(xì)看在這段代碼中,我們定義了一個 Person 類,然后在類中定義了類屬性 name 和 age,它們的類型分別是 CharField 和 IntegerField,之后我們初始化 Person 實例,然后通過實例獲取 name 和 age 屬性,輸出的卻是 str 和 int,而不再是 CharField 和 IntegerField
          能做到這樣的秘密就在于,Person 類在創(chuàng)建時,它的邏輯交給了另一個類,這個類針對類屬性進(jìn)行了轉(zhuǎn)換,最終變成對象與數(shù)據(jù)表的映射,通過轉(zhuǎn)換映射,我們就可以通過實例屬性的方式,友好地訪問表中對應(yīng)的字段值了。

          總結(jié)

          總結(jié)一下,這篇文章我們講了元類的實現(xiàn)原理,了解到元類是創(chuàng)建所有類的根源,我們可以通過 type 方法,或者在類中定義 __metaclass__ 的方式,把創(chuàng)建類的過程交給外部。
          當(dāng)使用 __metaclass__ 創(chuàng)建類時,它可以是一個方法,也可以是一個類。我們通常會使用類的方式去實現(xiàn)一個元類,這樣做更方便我們組織代碼,實現(xiàn)面向?qū)ο蟆?/span>
          在使用元類創(chuàng)建一個類時,我們可以修改創(chuàng)建類的細(xì)節(jié),例如對屬性做統(tǒng)一的轉(zhuǎn)換,或者增加新的方法等等,這對于我們開發(fā)一個復(fù)雜功能的類很友好,它可以把創(chuàng)建類的細(xì)節(jié)屏蔽在元類中,所以元類常常用在優(yōu)秀的開源框架中。、

          更多閱讀



          2020 年最佳流行 Python 庫 Top 10


          2020 Python中文社區(qū)熱門文章 Top 10


          5分鐘快速掌握 Python 定時任務(wù)框架

          特別推薦




          點擊下方閱讀原文加入社區(qū)會員

          瀏覽 46
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产一级A片特黄 | 91国精品 | 超碰在线不卡 | 超碰资源日韩久久 | 成人美女视频 |