Python 中的元類到底是什麼?這篇恐怕是最清楚的了
類作為對象
在理解元類之前,您需要掌握 Python 的類。Python 從 Smalltalk 語言中借用了一個非常特殊的類概念。
在大多數語言中,類只是描述如何產生對象的代碼段。在 Python 中也是如此:
>>> class ObjectCreator(object):
... pass
...
>>> my_object = ObjectCreator()
>>> print(my_object)
<__main__.ObjectCreator object at 0x8974f2c>
但是Python的類更甚。在Python中,Python的類也是對象。
對的,也是對象。
一旦使用關鍵字class
,Python 就會執行它並創建一個對象。示例代碼:
>>> class ObjectCreator(object):
... pass
...
如上代碼在內存中創建一個名稱為 「ObjectCreator」 的對象。
這個對象(類)本身具有創建對象(實例)的能力,這就是為什麼它也是一個類。
但是,它仍然是一個對象,因為:
- 您可以將其分配給變量
- 你可以複製它
- 您可以為其添加屬性
- 您可以將其作為函數參數傳遞
例如:
>>> 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>
動態創建類
由於類是對象,因此您可以像創建任何對象一樣即時創建它們。
首先,您可以使用class
以下方法在函數中創建一個類:
>>> 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 0x89c6d4c>
但這並不是那麼動態,因為您仍然必須自己編寫整個類。
由於類是對象,因此它們必須由某種東西生成。
使用class
關鍵字時,Python 會自動創建此對象。但是,與 Python 中的大多數事情一樣,它為您提供了一種手動進行操作的方法。
還記得功能type
嗎?這個函數可以讓您知道對象的類型:
>>> print(type(1))
<type 'int'>
>>> print(type("1"))
<type 'str'>
>>> print(type(ObjectCreator))
<type 'type'>
>>> print(type(ObjectCreator()))
<class '__main__.ObjectCreator'>
嗯,type
具有完全不同的功能,它也可以動態創建類。type
可以將類的描述作為參數,並返回一個類。
(我知道,根據傳遞給它的參數,同一個函數可以有兩種完全不同的用法是很愚蠢的。由於 Python 中的向後兼容性,這是一個問題)
type
用法:
type(name, bases, attrs)
參數:
name
:Class名稱bases
:父類的元組(對於繼承,可以為空)attrs
:包含屬性名稱和值的字典
例如:
>>> class MyShinyClass(object):
... pass
可以通過以下方式手動創建:
>>> MyShinyClass = type('MyShinyClass', (), {}) # 返回一個類對象
>>> print(MyShinyClass)
<class '__main__.MyShinyClass'>
>>> print(MyShinyClass()) # 創建類的實例
<__main__.MyShinyClass object at 0x8997cec>
您會注意到,我們使用 「MyShinyClass」 作為類的名稱和變量來保存類引用。
type
接受字典來定義類的屬性。所以:
>>> 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
當然,您可以從中繼承,因此:
>>> class FooChild(Foo):
... pass
將是:
>>> FooChild = type('FooChild', (Foo,), {})
>>> print(FooChild)
<class '__main__.FooChild'>
>>> print(FooChild.bar) # bar is inherited from Foo
True
最終,您需要向類中添加方法。只需定義具有適當簽名的函數並將其分配為屬性即可。
>>> 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 中,類是對象,您可以動態動態地創建一個類。
這是 Python 在使用關鍵字class
時所做的,並且是通過使用元類來完成的。
什麼是元類(最終)
元類是創建類的 「東西」。
您定義類是為了創建對象,對嗎?
但是我們了解到 Python 類是對象。
好吧,元類就是創建這些對象的原因。它們是類的類,您可以通過以下方式描繪它們:
MyClass = MetaClass()
my_object = MyClass()
您已經看到,type
您可以執行以下操作:
MyClass = type('MyClass', (), {})
這是因為該函數type
實際上是一個元類。type
是 Python 用於在幕後創建所有類的元類。
現在,您想知道為什麼用小寫而不是小寫Type
?
好吧,我想這與str
創建字符串對象int
的類和創建整數對象的類的一致性有關。type
只是創建類對象的類。
您可以通過檢查__class__
屬性來看到。
一切,我的意思是一切,都是 Python 中的對象。其中包括整數,字符串,函數和類。它們都是對象。所有這些都是從一個類創建的:
>>> 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'>
因此,元類只是創建類對象的東西。
如果願意,可以將其稱為 「類工廠」。
type
是 Python 使用的內置元類,但是您當然可以創建自己的元類。
該__metaclass__
屬性
在 Python 2 中,您可以__metaclass__
在編寫類時添加屬性(有關 Python 3 語法,請參見下一部分):
class Foo(object):
__metaclass__ = something...
[...]
如果這樣做,Python 將使用元類創建類Foo
。
小心點,這很棘手。
您class Foo(object)
先編寫,但Foo
尚未在內存中創建類對象。
Python 將__metaclass__
在類定義中尋找。如果找到它,它將使用它來創建對象類Foo
。如果沒有,它將 type
用於創建類。
讀幾次。
當您這樣做時:
class Foo(Bar):
pass
Python 執行以下操作:
中有__metaclass__
屬性Foo
嗎?
如果是的話,在內存中創建一個類對象(我說的是類對象,陪在我身邊在這裡),名稱Foo
使用是什麼__metaclass__
。
如果 Python 找不到__metaclass__
,它將__metaclass__
在 MODULE 級別上查找,並嘗試執行相同的操作(但僅適用於不繼承任何內容的類,基本上是老式的類)。
然後,如果根本找不到任何對象__metaclass__
,它將使用Bar
的(第一個父對象)自己的元類(可能是默認值type
)創建類對象。
請注意,該__metaclass__
屬性將不會被繼承,父(Bar.__class__
)的元類將被繼承。如果Bar
使用通過(而不是)__metaclass__
創建的屬性,則子類將不會繼承該行為。Bar``type()``type.__new__()
現在最大的問題是,您可以輸入__metaclass__
什麼?
答案是:可以創建類的東西。
什麼可以創建一個類?type
,或任何繼承或使用它的內容。
Python 3 中的元類
設置元類的語法在 Python 3 中已更改:
class Foo(object, metaclass=something):
...
即__metaclass__
不再使用該屬性,而在基類列表中使用關鍵字參數。
在 python 3 中添加到元類的一件事是,您還可以將屬性作為關鍵字參數傳遞給元類,如下所示:
class Foo(object, metaclass=something, kwarg1=value1, kwarg2=value2):
...
為什麼要使用元類?
現在是個大問題。為什麼要使用一些晦澀的易錯功能?
好吧,通常您不會:
元類是更深層的魔術,99%的用戶永遠不必擔心。如果您想知道是否需要它們,則不需要(實際上需要它們的人肯定會知道他們需要它們,並且不需要解釋原因)。
Python 大師 Tim Peters
元類的主要用例是創建 API。一個典型的例子是 Django ORM。它允許您定義如下內容:
class Person(models.Model):
name = models.CharField(max_length=30)
age = models.IntegerField()
但是,如果您這樣做:
person = Person(name='bob', age='35')
print(person.age)
它不會返回IntegerField
對象。它將返回int
,甚至可以直接從數據庫中獲取它。
這是可能的,因為models.Model
define __metaclass__
並使用了一些魔術,這些魔術將使Person
您使用簡單語句定義的對象變成與數據庫字段的複雜掛鈎。
Django 通過公開一個簡單的 API 並使用元類,從該 API 重新創建代碼來完成幕後的實際工作,使看起來複雜的事情變得簡單。
最後一點
首先,您知道類是可以創建實例的對象。
實際上,類本身就是元類的實例。
>>> class Foo(object): pass
>>> id(Foo)
一切都是 Python 中的對象,它們都是類的實例或元類的實例。
除了type
。
type
實際上是它自己的元類。
其次,元類很複雜。您可能不希望將它們用於非常簡單的類更改。您可以使用兩種不同的技術來更改類:
- 猴子修補
- 類裝飾
99%的時間,您需要更改類,最好使用這些。
但是 98%的時間根本不需要更改類。
本文首發於BigYoung小站