追溯Python類的鼻祖——元類

Python中萬物皆對象

Python是一門面向對象的語言,因此Python中數字、字符串、列表、集合、字典、函數、類等都是對象。python


利用 type() 來查看Python中的各對象類型git

In [11]: # 數字

In [12]: type(10)
Out[12]: int

In [13]: type(3.1415926)
Out[13]: float

In [14]: # 字符串

In [15]: type('a')
Out[15]: str

In [16]: type("abc")
Out[16]: str

In [17]: # 列表

In [18]: type(list)
Out[18]: type

In [19]: type([])
Out[19]: list

In [20]: # 集合

In [21]: type(set)
Out[21]: type

In [22]: my_set = {1, 2, 3}

In [23]: type(my_set)
Out[23]: set

In [24]: # 字典

In [25]: type(dict)
Out[25]: type

In [26]: my_dict = {'name': 'hui'}

In [27]: type(my_dict)
Out[27]: dict

In [28]: # 函數

In [29]: def func():
    ...:     pass
    ...:

In [30]: type(func)
Out[30]: function

In [31]: # 類

In [32]: class Foo(object):
    ...:     pass
    ...:

In [33]: type(Foo)
Out[33]: type

In [34]: f = Foo()

In [35]: type(f)
Out[35]: __main__.Foo

In [36]: # type

In [37]: type(type)
Out[37]: type

複製代碼

能夠看出程序員

  • 數字 1int類型 的對象
  • 字符串 abcstr類型 的對象
  • 列表、集合、字典是 type類型 的對象,其建立出來的對象才分別屬於 list、set、dict 類型
  • 函數 funcfunction類型 的對象
  • 自定義類 Foo 建立出來的對象 fFoo 類型,其類自己 Foo 則是 type類型 的對象。
  • type 自己都是type類型的對象

1. 類也是對象

類就是擁有相等功能和相同的屬性的對象的集合web

在大多數編程語言中,類就是一組用來描述如何生成一個對象的代碼段。在 Python 中這一點仍然成立:編程

In [1]: class ObjectCreator(object):
   ...:     pass
   ...:

In [2]: my_object = ObjectCreator()

In [3]: print(my_object)
<__main__.ObjectCreator object at 0x0000021257B5A248>

複製代碼

可是,Python中的類還遠不止如此。類一樣也是一種對象。是的,沒錯,就是對象。只要你 使用關鍵字 class,Python解釋器在執行的時候就會建立一個對象markdown

下面的代碼段:app

>>> class ObjectCreator(object):pass複製代碼

將在內存中建立一個對象,名字就是 ObjectCreator。這個 對象(類對象ObjectCreator)擁有建立對象(實例對象)的能力。可是,它的本質仍然是一個對象,因而乎你能夠對它作以下的操做:編程語言

  1. 你能夠將它賦值給一個變量
  2. 你能夠拷貝它
  3. 你能夠爲它增長屬性
  4. 你能夠將它做爲函數參數進行傳遞

以下示例:svg

In [39]: class ObjectCreator(object):
    ...:     pass
    ...:

In [40]: print(ObjectCreator)
<class '__main__.ObjectCreator'> In [41]:# 看成參數傳遞

In [41]: def out(obj):
    ...:     print(obj)
    ...:

In [42]: out(ObjectCreator)
<class '__main__.ObjectCreator'> In [43]: # hasattr 判斷一個類是否有某種屬性

In [44]: hasattr(ObjectCreator, 'name')
Out[44]: False

In [45]: # 新增類屬性

In [46]: ObjectCreator.name = 'hui'

In [47]: hasattr(ObjectCreator, 'name')
Out[47]: True

In [48]: ObjectCreator.name
Out[48]: 'hui'

In [49]: # 將類賦值給變量

In [50]: obj = ObjectCreator

In [51]: obj()
Out[51]: <__main__.ObjectCreator at 0x212596a7248>

In [52]:
複製代碼

2. 動態地建立類

由於類也是對象,你能夠在運行時動態的建立它們,就像其餘任何對象同樣。首先,你能夠在函數中建立類,使用 class 關鍵字便可。函數

def cls_factory(cls_name):
    """ 建立類工廠 :param: cls_name 建立類的名稱 """
    if cls_name == 'Foo':
        class Foo():
            pass
        return Foo  # 返回的是類,不是類的實例

    elif cls_name == 'Bar':
        class Bar():
            pass
        return Bar
複製代碼

IPython 測驗

MyClass = cls_factory('Foo')

In [60]: MyClass
Out[60]: __main__.cls_factory.<locals>.Foo # 函數返回的是類,不是類的實例

In [61]: MyClass()
Out[61]: <__main__.cls_factory.<locals>.Foo at 0x21258b1a9c8>

複製代碼

但這還不夠動態,由於你仍然須要本身編寫整個類的代碼。因爲類也是對象,因此它們必須是經過什麼東西來生成的纔對。

當你使用class關鍵字時,Python解釋器自動建立這個對象。但就和Python中的大多數事情同樣,Python仍然提供給你手動處理的方法。


3. 使用 type 建立類

type 還有一種徹底不一樣的功能,動態的建立類。

type能夠接受一個類的描述做爲參數,而後返回一個類。(要知道,根據傳入參數的不一樣,同一個函數擁有兩種徹底不一樣的用法是一件很傻的事情,但這在Python中是爲了保持向後兼容性)

type 能夠像這樣工做:

type(類名, 由父類名稱組成的元組(針對繼承的狀況,能夠爲空),包含屬性的字典(名稱和值))

好比下面的代碼:

In [63]: class Test:
    ...:     pass
    ...:

In [64]: Test()
Out[64]: <__main__.Test at 0x21258b34048>

In [65]:
複製代碼

能夠手動像這樣建立:

In [69]:# 使用type定義類

In [69]: Test2 = type('Test2', (), {})

In [70]: Test2()
Out[70]: <__main__.Test2 at 0x21259665808>
複製代碼

咱們使用 Test2 做爲類名,而且也能夠把它當作一個變量來做爲類的引用。類和變量是不一樣的,這裏沒有任何理由把事情弄的複雜。即 type函數 中第1個實參,也能夠叫作其餘的名字,這個名字表示類的名字

In [71]: UserCls = type('User', (), {})

In [72]: print(UserCls)
<class '__main__.User'> In [73]:
複製代碼

使用 help 來測試這2個類

In [74]: # 用 help 查看 Test類

In [75]: help(Test)
Help on class Test in module __main__:

class Test(builtins.object) | Data descriptors defined here:
 |
 |  __dict__
 |      dictionary for instance variables (if defined)
 |
 |  __weakref__
 |      list of weak references to the object (if defined)


In [76]: # 用 help 查看 Test2類

In [77]: help(Test2)
Help on class Test2 in module __main__:

class Test2(builtins.object) | Data descriptors defined here:
 |
 |  __dict__
 |      dictionary for instance variables (if defined)
 |
 |  __weakref__
 |      list of weak references to the object (if defined)


In [78]:
複製代碼

4. 使用type建立帶有屬性的類

type 接受一個字典來爲類定義屬性,所以

Parent = type('Parent', (), {'name': 'hui'})
複製代碼

能夠翻譯爲:

class Parent(object):
	name = 'hui'
複製代碼

而且能夠將 Parent 當成一個普通的類同樣使用:

In [79]: Parent = type('Parent', (), {'name': 'hui'})

In [80]: print(Parent)
<class '__main__.Parent'> In [81]: Parent.name
Out[81]: 'hui'

In [82]: p = Parent()

In [83]: p.name
Out[83]: 'hui'

複製代碼

固然,你能夠繼承這個類,代碼以下:

class Child1(Parent):
    name = 'jack'
    sex =  '男'
    
class Child2(Parent):
    name = 'mary'
    sex = '女'
複製代碼

就能夠寫成:

Child1 = type('Child1', (Parent, ), {'name': 'jack', 'sex': '男'})

In [85]: Child2 = type('Child2', (Parent, ), {'name': 'mary', 'sex': '女'})

In [87]: Child1.name, Child1.sex
Out[87]: ('jack', '男')

In [88]: Child2.name, Child2.sex
Out[88]: ('mary', '女')
複製代碼

注意:

  • type 的第2個參數,元組中是父類的名字,而不是字符串
  • 添加的屬性是 類屬性,並非實例屬性

5. 使用type建立帶有方法的類

最終你會但願爲你的類增長方法。只須要定義一個有着恰當簽名的函數並將其做爲屬性賦值就能夠了。

添加實例方法

In [89]: Parent = type('Parent', (), {'name': 'hui'})

In [90]: # 定義函數

In [91]: def get_name(self):
    ...:     return self.name
    ...:

In [92]: Child3 = type('Child3', (Parent, ), {'name': 'blob', 'get_name': get_name})

In [93]: c3 = Child3()

In [94]: c3.name
Out[94]: 'blob'

In [95]: c3.get_name()
Out[95]: 'blob'
複製代碼

添加靜態方法

In [96]: Parent = type('Parent', (), {'name': 'hui'})

In [97]: # 定義靜態方法
    
In [98]: @staticmethod
    ...: def test_static():
    ...:     print('static method called...')
    ...:

In [100]: Child4 = type('Child4', (Parent, ), {'name': 'zhangsan', 'test_static': test_static})

In [101]: c4 = Child4()

In [102]: c4.test_static()
static method called...

In [103]: Child4.test_static()
static method called...
複製代碼

添加類方法

In [105]: Parent = type('Parent', (), {'name': 'hui'})

In [106]: # 定義類方法

In [107]: @classmethod
     ...: def test_class(cls):
     ...:     print(cls.name)
     ...:

In [108]: Child5 = type('Child5', (Parent, ), {'name': 'lisi', 'test_class': test_class})

In [109]: c5 = Child5()

In [110]: c5.test_class()
lisi

In [111]: Child5.test_class()
lisi
複製代碼

你能夠看到,在Python中,類也是對象,你能夠動態的建立類。這就是當你使用關鍵字 classPython 在幕後作的事情,就是經過元類來實現的

較爲完整的使用 type 建立類的方式:

class Animal(object):
    
    def eat(self):
        print('吃東西')


def dog_eat(self):
    print('喜歡吃骨頭')

def cat_eat(self):
    print('喜歡吃魚')


Dog = type('Dog', (Animal, ), {'tyep': '哺乳類', 'eat': dog_eat})

Cat = type('Cat', (Animal, ), {'tyep': '哺乳類', 'eat': cat_eat})

# ipython 測驗
In [125]: animal = Animal()

In [126]: dog = Dog()

In [127]: cat = Cat()

In [128]: animal.eat()
吃東西

In [129]: dog.eat()
喜歡吃骨頭

In [130]: cat.eat()
喜歡吃魚

複製代碼

6. 到底什麼是元類(終於到主題了)

元類就是用來建立類的【東西】。你建立類就是爲了建立類的實例對象,不是嗎?可是咱們已經學習到了Python中的類也是對象。

元類就是用來建立這些類(對象)的,元類就是類的類,你能夠這樣理解爲:

MyClass = MetaClass() # 使用元類建立出一個對象,這個對象稱爲「類」
my_object = MyClass() # 使用「類」來建立出實例對象
複製代碼

你已經看到了type可讓你像這樣作:

MyClass = type('MyClass', (), {})
複製代碼

這是由於函數 type 其實是一個元類。type 就是 Python在背後用來建立全部類的元類。如今你想知道那爲何 type 會所有采用小寫形式而不是 Type 呢?好吧,我猜這是爲了和 str 保持一致性,str是用來建立字符串對象的類,而 int 是用來建立整數對象的類。type 就是建立類對象的類。你能夠經過檢查 __class__ 屬性來看到這一點。所以 Python中萬物皆對象

如今,對於任何一個 __class____class__ 屬性又是什麼呢?

In [136]: a = 10

In [137]: b = 'acb'

In [138]: li = [1, 2, 3]

In [139]: a.__class__.__class__
Out[139]: type

In [140]: b.__class__.__class__
Out[140]: type

In [141]: li.__class__.__class__
Out[141]: type

In [142]: li.__class__.__class__.__class__
Out[142]: type
    
複製代碼

所以,元類就是建立類這種對象的東西。type 就是 Python的內建元類,固然了,你也能夠建立本身的元類。


7. __metaclass__ 屬性

你能夠在定義一個類的時候爲其添加 __metaclass__ 屬性。

class Foo(object):
    __metaclass__ = something…
    ...省略...
複製代碼

若是你這麼作了,Python就會用元類來建立類Foo。當心點,這裏面有些技巧。你首先寫下 class Foo(object),可是類Foo尚未在內存中建立。Python會在類的定義中尋找 __metaclass__ 屬性,若是找到了,Python就會用它來建立類Foo,若是沒有找到,就會用內建的 type 來建立這個類。

class Foo(Bar):
    pass
複製代碼

Python作了以下的操做:

  1. Foo中有 __metaclass__ 這個屬性嗎?若是有,Python會經過 __metaclass__ 建立一個名字爲Foo的類(對象)
  2. 若是Python沒有找到 __metaclass__,它會繼續在 Bar(父類) 中尋找 __metaclass__ 屬性,並嘗試作和前面一樣的操做。
  3. 若是Python在任何父類中都找不到 __metaclass__,它就會在模塊層次中去尋找 __metaclass__,並嘗試作一樣的操做。
  4. 若是仍是找不到 __metaclass__ ,Python就會用內置的 type 來建立這個類對象。

如今的問題就是,你能夠在 __metaclass__ 中放置些什麼代碼呢?

答案就是:能夠建立一個類的東西。那麼什麼能夠用來建立一個類呢?type,或者任何使用到type或者子類化的type均可以。


8. 自定義元類

元類的主要目的就是爲了當建立類時可以自動地改變類。

假想一個很傻的例子,你決定在你的模塊裏全部的類的屬性都應該是大寫形式。有好幾種方法能夠辦到,但其中一種就是經過在模塊級別設定 __metaclass__。採用這種方法,這個模塊中的全部類都會經過這個元類來建立,咱們只須要告訴元類把全部的屬性都改爲大寫形式就萬事大吉了。

幸運的是,__metaclass__ 實際上能夠被任意調用,它並不須要是一個正式的類。因此,咱們這裏就先以一個簡單的函數做爲例子開始。


python2中

# -*- coding:utf-8 -*-
def upper_attr(class_name, class_parents, class_attr):

    # class_name 會保存類的名字 Foo
    # class_parents 會保存類的父類 object
    # class_attr 會以字典的方式保存全部的類屬性

    # 遍歷屬性字典,把不是__開頭的屬性名字變爲大寫
    new_attr = {}
    for name, value in class_attr.items():
        if not name.startswith("__"):
            new_attr[name.upper()] = value

    # 調用type來建立一個類
    return type(class_name, class_parents, new_attr)

class Foo(object):
    __metaclass__ = upper_attr # 設置Foo類的元類爲upper_attr
    bar = 'bip'

print(hasattr(Foo, 'bar'))
# Flase
print(hasattr(Foo, 'BAR'))
# True

f = Foo()
print(f.BAR)
複製代碼

python3中

# -*- coding:utf-8 -*-
def upper_attr(class_name, class_parents, class_attr):

    #遍歷屬性字典,把不是__開頭的屬性名字變爲大寫
    new_attr = {}
    for name,value in class_attr.items():
        if not name.startswith("__"):
            new_attr[name.upper()] = value

    #調用type來建立一個類
    return type(class_name, class_parents, new_attr)

# 再類的繼承()中使用metaclass
class Foo(object, metaclass=upper_attr):
    bar = 'bip'

print(hasattr(Foo, 'bar'))
# Flase
print(hasattr(Foo, 'BAR'))
# True

f = Foo()
print(f.BAR)
複製代碼

再作一次,這一次用一個真正的 class 來當作元類。

class UpperAttrMetaClass(type):
    
    def __new__(cls, class_name, class_parents, class_attr):
        # 遍歷屬性字典,把不是__開頭的屬性名字變爲大寫
        new_attr = {}
        for name, value in class_attr.items():
            if not name.startswith("__"):
                new_attr[name.upper()] = value

        # 方法1:經過'type'來作類對象的建立
        return type(class_name, class_parents, new_attr)

        # 方法2:複用type.__new__方法
        # 這就是基本的OOP編程,沒什麼魔法
        # return type.__new__(cls, class_name, class_parents, new_attr)

        
# python3的用法
class Foo(object, metaclass=UpperAttrMetaClass):
    bar = 'bip'

# python2的用法
class Foo(object):
	__metaclass__ = UpperAttrMetaClass
    bar = 'bip'


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

f = Foo()
print(f.BAR)
# 輸出: 'bip'
複製代碼

__new__ 是在__init__以前被調用的特殊方法
__new__是用來建立對象並返回之的方法
而__init__只是用來將傳入的參數初始化給對象
這裏,建立的對象是類,咱們但願可以自定義它,因此咱們這裏改寫__new__
複製代碼

就是這樣,除此以外,關於元類真的沒有別的可說的了。但就元類自己而言,它們實際上是很簡單的:

  1. 攔截類的建立
  2. 修改類
  3. 返回修改以後的類

究竟爲何要使用元類?

如今回到咱們的大主題上來,到底是爲何你會去使用這樣一種容易出錯且晦澀的特性?

好吧,通常來講,你根本就用不上它:

「元類就是深度的魔法,99%的用戶應該根本沒必要爲此操心。若是你想搞清楚到底是否須要用到元類,那麼你就不須要它。那些實際用到元類的人都很是清楚地知道他們須要作什麼,並且根本不須要解釋爲何要用元類。」 —— Python界的領袖 Tim Peters


源代碼

源代碼已上傳到 Gitee PythonKnowledge: Python知識寶庫,歡迎你們來訪。

✍ 碼字不易,還望各位大俠多多支持❤️。


公衆號

新建文件夾X

大天然用數百億年創造出咱們現實世界,而程序員用幾百年創造出一個徹底不一樣的虛擬世界。咱們用鍵盤敲出一磚一瓦,用大腦構建一切。人們把1000視爲權威,咱們反其道行之,捍衛1024的地位。咱們不是鍵盤俠,咱們只是平凡世界中不凡的締造者 。

相關文章
相關標籤/搜索