自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

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

開發(fā) 后端
這篇文章,我們就來看一下 Python 元類的來龍去脈。

[[388869]]

 如果你看過比較優(yōu)秀的 Python 開源框架,肯定見到過元類的身影。例如,在一個類中定義了類屬性 __metaclass__,這就說明這個類使用了元類來創(chuàng)建。

那元類的實現(xiàn)原理究竟是怎樣的?使用元類能幫我們在開發(fā)中解決什么樣的問題?

這篇文章,我們就來看一下 Python 元類的來龍去脈。

什么是元類?

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

  1. class Person(object)  
  2.     def __init__(name):  
  3.         self.name = name  
  4. p = Person('zhangsan') 

那你有沒有想過,我們平時定義的類,它是如何創(chuàng)建出來的?

別著急,我們先來看一個例子: 

  1. >>> a = 1               # 創(chuàng)建a的類是int a是int的實例  
  2. >>> a.__class__  
  3. <type 'int'>  
  4. >>> b = 'abc'           # 創(chuàng)建b的類是str b是str的實例  
  5. >>> b.__class__  
  6. <type 'str'>  
  7. >>> def c():            # 創(chuàng)建c的類是function 方法c是function的實例  
  8. ...     pass  
  9. >>> c.__class__  
  10. <type 'function'>  
  11. >>> class D(object):    # 創(chuàng)建d的類是D d是D的實例  
  12. ...     pass  
  13. >>> d.__class__  
  14. <class '__main__.D'> 

在這個例子中,我們定義了 int、str、function、class,然后分別調(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ā)中使用到的例如 list、dict 也類似,你可以測試觀察一下結(jié)果。

現(xiàn)在我們已經(jīng)得知,創(chuàng)建這些實例的類是 int、str、function、class,那進(jìn)一步思考一下,這些類又是怎么創(chuàng)建出來的呢?

同樣地,我們也調(diào)用這些類的 __class__ 方法,觀察結(jié)果: 

  1. >>> a = 1 
  2. >>> a.__class__.__class__  
  3. <type 'type'>  
  4. >>>  
  5. >>> b = 'abc'  
  6. >>> b.__class__.__class__  
  7. <type 'type'>  
  8. >>>  
  9. >>> def c():  
  10. ...     pass  
  11. >>> c.__class__.__class__ 
  12. <type 'type'>  
  13. >>>  
  14. >>> class D(object):  
  15. ...     pass  
  16. >>> d = D()  
  17. >>> d.__class__.__class__  
  18. <type 'type'> 

從結(jié)果我們可以看到,創(chuàng)建這些類的類,都是 type,所以 type 就是創(chuàng)建所有類的「元類」。也就是說,元類的作用就是用來創(chuàng)建類的。

你可以這樣理解:

  1.  元類 -> 類
  2.  類 -> 實例

用偽代碼表示,就是下面這樣: 

  1. klass = MetaClass()     # 元類創(chuàng)建類  
  2. obj = klass()           # 類創(chuàng)建實例 

是不是很有意思?

在這里,你也可以感受一下這句話的含義:Python 中一切皆對象!

無論是普通類型、方法、實例,還是類,都可以統(tǒng)一看作對象,它們的起源就是元類。

其實,在 Python 中,使用 type 方法,我們可就以創(chuàng)建出一個類,type 方法的語法如下: 

  1. type(class_name, (base_class, ...), {attr_key: attr_value, ...}) 

例如,像下面這樣,我們使用 type 方法創(chuàng)建 MyClass 類,并且讓它繼承 object: 

  1. >>> A = type('MyClass', (object, ), {}) # type創(chuàng)建一個類,繼承object  
  2. >>> A  
  3. <class '__main__.MyClass'>  
  4. >>> A()  
  5. <__main__.MyClass object at 0x10d905950> 

我們還可以使用 type 創(chuàng)建一個包含屬性和方法的類: 

  1. >>> def foo(self):  
  2. ...     return 'foo'  
  3. ...  
  4. >>> name = 'zhangsan'  
  5. >>>  
  6. # type 創(chuàng)建類B 繼承object 包含 name 屬性和 foo 方法  
  7. >>> B = type('MyClass', (object, ), {'name': name, 'foo': foo})   
  8. >>> B.name          # 打印 name 屬性  
  9. 'zhangsan'  
  10. >>> print B().foo() # 調(diào)用 foo 方法  
  11. foo 

通過 type 方法創(chuàng)建的類,和我們自己定義一個類,在使用上沒有任何區(qū)別。

其實,除了使用 type 方法創(chuàng)建一個類之外,我們還可以使用類屬性 __metaclass__ 創(chuàng)建一個類,這就是下面要講的「自定義元類」。

自定義元類

我們可以使用類屬性 __metaclass__ 把一個類的創(chuàng)建過程,轉(zhuǎn)交給其它地方,可以像下面這樣寫: 

  1. class A(object):  
  2.     __metaclass__ = ... # 這個類的創(chuàng)建轉(zhuǎn)交給其他地方  
  3.     pass 

這個例子中,我們先定義了類 A,然后定義了一個類屬性 __metaclass__,這個屬性表示創(chuàng)建類 A 的過程,轉(zhuǎn)交給其它地方處理。

那么,這個類屬性 __metaclass__ 需要怎么寫呢?

其實,它可以是一個方法,也可以是一個類。

用方法創(chuàng)建類

如果類屬性 __metaclass__ 賦值的是一個方法,那么創(chuàng)建類的過程,就交給了一個方法來執(zhí)行。 

  1. def create_class(name, bases, attr):  
  2.     print 'create class by method...'  
  3.     # 什么事都沒做 直接用type創(chuàng)建了一個類  
  4.     return type(name, bases, attr)  
  5. class A(object):  
  6.     # 創(chuàng)建類的過程交給了一個方法  
  7.     __metaclass__ = create_class  
  8. # Output:      
  9. # 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)建另一個類。 

  1. class B(type):  
  2.     # 必須定義 __new__ 方法 返回一個類  
  3.     def __new__(cls, name, bases, attr):  
  4.         print 'create class by B ...'  
  5.         return type(name, bases, attr)  
  6. class A(object):  
  7.     # 創(chuàng)建類的過程交給了B  
  8.     __metaclass__ = B   
  9.  # Output:  
  10. # 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ū)ο筇匦?/li>
  •  使用類可以更好地組織代碼結(jié)構(gòu)

另外,使用類創(chuàng)建一個類時,這里有一個優(yōu)化點:在 __new__ 方法中不建議直接調(diào)用 type 方法,而是建議調(diào)用 super 的 __new__ 來創(chuàng)建類,執(zhí)行結(jié)果與 type 方法是一樣的: 

  1. class B(type):  
  2.     def __new__(cls, name, bases, attr):  
  3.         # 使用 super.__new__ 創(chuàng)建類  
  4.         return super(B, cls).__new__(cls, name, bases, attr)     

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

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

我們看下面這個例子: 

  1. # coding: utf8  
  2. class Meta(type):  
  3.     def __new__(cls, name, bases, attr):  
  4.         # 通過 Meta 創(chuàng)建的類 屬性會都變成大寫  
  5.         for k, v in attr.items():  
  6.             if not k.startswith('__'):  
  7.                 attr[k] = v.upper()  
  8.             else:  
  9.                 attr[k] = v  
  10.         return type(name, bases, attr)  
  11. class A(object):  
  12.     # 通過 Meta 創(chuàng)建類  
  13.     __metaclass__ = Meta  
  14.     name = 'zhangsan'  
  15. class B(object):  
  16.     # 通過 Meta 創(chuàng)建類  
  17.     __metaclass__ = Meta   
  18.     name = 'lisi'  
  19. # 打印類屬性 會自動變成大寫  
  20. print A.name    # ZHANGSAN  
  21. 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 ORM、peewee,下面是使用 Django ORM 定義一個數(shù)據(jù)表映射類的代碼: 

  1. class Person(models.Model):  
  2.     # 注意: name 和 age 是類屬性  
  3.     name = models.CharField(max_length=30 
  4.     age = models.IntegerField()      
  5. person = Person(name='zhangsan'age=20 
  6. print person.name   # zhangsan  
  7. 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ū)ο蟆?/p>

在使用元類創(chuàng)建一個類時,我們可以修改創(chuàng)建類的細(xì)節(jié),例如對屬性做統(tǒng)一的轉(zhuǎn)換,或者增加新的方法等等,這對于我們開發(fā)一個復(fù)雜功能的類很友好,它可以把創(chuàng)建類的細(xì)節(jié)屏蔽在元類中,所以元類常常用在優(yōu)秀的開源框架中。、 

 

責(zé)任編輯:龐桂玉 來源: Python中文社區(qū) (ID:python-china)
相關(guān)推薦

2023-12-16 13:21:00

Python元類ORM

2024-07-30 11:29:09

2011-03-24 09:34:41

SPRING

2016-10-25 14:27:32

metaclasspython

2022-09-07 10:20:05

Python裝飾類

2019-12-23 09:13:11

Python數(shù)據(jù)語言

2016-09-06 19:32:11

PythonWeb

2011-04-12 14:58:23

加密解密類

2021-05-28 18:12:51

C++設(shè)計

2015-08-06 15:13:49

runtimeIOS開發(fā)

2015-09-08 11:06:46

設(shè)計編輯窗體

2020-12-08 06:23:05

LockSupport線程工具

2024-05-09 08:08:32

SpringBinderJava

2022-01-26 15:20:00

配置微服務(wù)架構(gòu)

2024-05-20 09:26:42

Python裝飾器函數(shù)

2020-10-13 09:33:28

AI神經(jīng)元人類

2023-03-13 14:02:31

元宇宙

2012-07-17 17:05:55

JavaScript

2013-04-08 10:54:51

Javascript

2023-12-07 09:07:58

點贊
收藏

51CTO技術(shù)棧公眾號