每天一道 python 面試題 - Python 中的元類 (metaclass) 詳細版本

類作為物件

在理解元類之前,您需要掌握 Python 的類。Python 從 Smalltalk 語言中借用了一個非常特殊的類概念。

在大多數語言中,類只是描述如何產生物件的程式碼段。在 Python 中也是如此:

>>> class ObjectCreator(object):。。。 pass。。。>>> my_object = ObjectCreator()>>> print(my_object)<__main__。ObjectCreator object at 0x8974f2c>

但是類比 Python 中的更多。類也是物件。

一旦使用關鍵字

class

,Python 就會執行它並建立一個物件

>>> class ObjectCreator(object):。。。 pass。。。

在記憶體中建立一個名稱為“ ObjectCreator”的物件。

這個物件(類)本身具有建立物件(例項)的能力,這就是為什麼它是一個類

但是,它仍然是一個物件,因此:

您可以將其分配給變數

你可以複製它

您可以為其新增屬性

您可以將其作為函式引數傳遞

例如:

>>> print(ObjectCreator) # you can print a class because it‘s an object>>> def echo(o):。。。 print(o)。。。>>> echo(ObjectCreator) # you can pass a class as a parameter>>> print(hasattr(ObjectCreator, ’new_attribute‘))False>>> ObjectCreator。new_attribute = ’foo‘ # you can add attributes to a class>>> print(hasattr(ObjectCreator, ’new_attribute‘))True>>> print(ObjectCreator。new_attribute)foo>>> ObjectCreatorMirror = ObjectCreator # you can assign a class to a variable>>> 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 # return the class, not an instance。。。 else:。。。 class Bar(object):。。。 pass。。。 return Bar。。。>>> MyClass = choose_class(’foo‘)>>> print(MyClass) # the function returns a class, not an instance>>> print(MyClass()) # you can create an object from this class<__main__。Foo object at 0x89c6d4c>

但這並不是那麼動態,因為您仍然必須自己編寫整個類。

由於類是物件,因此它們必須由某種東西生成。

使用

class

關鍵字時,Python 會自動建立此物件。但是,與 Python 中的大多數事情一樣,它為您提供了一種手動進行操作的方法。

還記得功能

type

嗎?好的舊函式可以讓您知道物件的型別:

>>> print(type(1))>>> print(type(“1”))>>> print(type(ObjectCreator))>>> print(type(ObjectCreator()))

嗯,

type

具有完全不同的功能,它也可以動態建立類。

type

可以將類的描述作為引數,並返回一個類。

(我知道,根據傳遞給它的引數,同一個函式可以有兩種完全不同的用法是很愚蠢的。由於 Python 中的向後相容性,這是一個問題)

type

這樣工作:

type(name, bases, attrs)

name

:班級名稱

bases

:父類的元組(對於繼承,可以為空)

attrs

:包含屬性名稱和值得字典

例如:

>>> class MyShinyClass(object):。。。 pass

可以透過以下方式手動建立:

>>> MyShinyClass = type(’MyShinyClass‘, (), {}) # returns a class object>>> print(MyShinyClass)>>> print(MyShinyClass()) # create an instance with the class<__main__。MyShinyClass object at 0x8997cec>

您會注意到,我們使用“ MyShinyClass”作為類的名稱和變數來儲存類引用。它們可以不同,但是沒有理由使事情複雜化。

type

接受字典來定義類的屬性。所以:

>>> class Foo(object):。。。 bar = True

可以翻譯為:

>>> Foo = type(’Foo‘, (), {’bar‘:True})

並用作普通類:

>>> print(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)>>> print(FooChild。bar) # bar is inherited from FooTrue

最終,您需要向類中新增方法。只需定義具有適當簽名的函式並將其分配為屬性即可

>>> 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__>>> name = ’bob‘>>> name。__class__>>> def foo(): pass>>> foo。__class__>>> class Bar(object): pass>>> b = Bar()>>> b。__class__

現在,什麼是

__class__

任何

__class__

>>> age。__class__。__class__>>> name。__class__。__class__>>> foo。__class__。__class__>>> b。__class__。__class__

因此,元類只是建立類物件的東西。

如果願意,可以將其稱為“班級工廠”。

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

使用 in 中的名稱

__metaclass__

如果 Python 找不到

__metaclass__

,它將

__metaclass__

在 MODULE 級別查詢,並嘗試執行相同的操作(但僅適用於不繼承任何內容的類,基本上是老式的類)。

然後,如果根本找不到任何物件

__metaclass__

,它將使用

Bar

的(第一個父物件)自己的元類(可能是預設值

type

)建立類物件。

請注意,該

__metaclass__

屬性將不會被繼承,而父(

Bar。__class__

)的元類將被繼承。如果

Bar

使用透過(而不是)

__metaclass__

建立的屬性,則子類將不會繼承該行為。

Bartype()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): 。。。

閱讀以下部分,瞭解 python 如何處理此問題。

自定義元類

元類的主要目的是在建立類時自動更改它。

通常,您要對 API 進行此操作,在 API 中要建立與當前上下文匹配的類。

想象一個愚蠢的示例,在該示例中,您決定模組中的所有類的屬性都應大寫。有多種方法可以執行此操作,但是一種方法是

__metaclass__

在模組級別進行設定。

這樣,將使用此元類建立該模組的所有類,而我們只需要告訴元類將所有屬性都轉換為大寫即可。

幸運的是,

__metaclass__

實際上可以是任何可呼叫的,它不必是正式的類(我知道,名稱中帶有“ class”的東西不必是類,請弄清楚……但這很有用)。

因此,我們將從使用函式的簡單示例開始。

# 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_attrs): “”“ 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_attrs = { attr if attr。startswith(“__”) else attr。upper(): v for attr, v in future_class_attrs。items() } # let `type` do the class creation return type(future_class_name, future_class_parents, uppercase_attrs)__metaclass__ = upper_attr # this will affect all classes in the moduleclass 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‘

讓我們檢查:

>>> hasattr(Foo, ’bar‘)False>>> hasattr(Foo, ’BAR‘)True>>> Foo。BAR’bip‘

現在,讓我們做完全一樣的操作,但是對元類使用真實的類:

# remember that `type` is actually a class like `str` and `int`# so you can inherit from itclass 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_attrs): uppercase_attrs = { attr if attr。startswith(“__”) else attr。upper(): v for attr, v in future_class_attrs。items() } return type(future_class_name, future_class_parents, uppercase_attrs)

讓我們重寫上面的內容,但是現在有了更短,更實際的變數名,我們知道它們的含義了:

class UpperAttrMetaclass(type): def __new__(cls, clsname, bases, attrs): uppercase_attrs = { attr if attr。startswith(“__”) else attr。upper(): v for attr, v in attrs。items() } return type(clsname, bases, uppercase_attrs)

您可能已經注意到了額外的爭論

cls

。它沒有什麼特別的:

__new__

始終將其定義的類作為第一個引數。就像您有

self

將例項作為第一個引數接收的普通方法一樣,還是為類方法定義了類。

但這不是適當的 OOP。我們正在

type

直接致電,而不是覆蓋或致電父母的

__new__

。讓我們改為:

class UpperAttrMetaclass(type): def __new__(cls, clsname, bases, attrs): uppercase_attrs = { attr if attr。startswith(“__”) else attr。upper(): v for attr, v in attrs。items() } return type。__new__(cls, clsname, bases, uppercase_attrs)

透過使用

super

,我們可以使其更加整潔,這將簡化繼承(因為是的,您可以具有元類,從元類繼承,從型別繼承):

class UpperAttrMetaclass(type): def __new__(cls, clsname, bases, attrs): uppercase_attrs = { attr if attr。startswith(“__”) else attr。upper(): v for attr, v in attrs。items() } return super(UpperAttrMetaclass, cls)。__new__( cls, clsname, bases, uppercase_attrs)

在 python 3 中,如果您使用關鍵字引數進行此呼叫,例如:

class Foo(object, metaclass=MyMetaclass, kwarg1=value1): 。。。

它將在元類中轉換為使用它:

class MyMetaclass(type): def __new__(cls, clsname, bases, dct, kwargs1=default): 。。。

而已。實際上,關於元類的更多資訊。

使用元類編寫程式碼的複雜性背後的原因不是因為元類,而是因為您通常使用元類依靠自身,操縱繼承和諸如 var 之類的變數來做扭曲的事情

__dict__

確實,元類對於做黑魔法特別有用,因此也很複雜。但就其本身而言,它們很簡單:

攔截 class 建立

修改 class

返回修改後的類

為什麼要使用元類類而不是函式?

既然

__metaclass__

可以接受任何可呼叫物件,那麼為什麼要使用一個類,因為它顯然更復雜?

這樣做有幾個原因:

意圖很明確。閱讀時

UpperAttrMetaclass(type)

,您會知道接下來會發生什麼

您可以使用 OOP。元類可以繼承元類,重寫父方法。元類甚至可以使用元類。

如果您指定了元類類,但沒有元類函式,則該類的子類將是其元類的例項。

您可以更好地構建程式碼。絕對不要像上面的示例那樣將元類用於瑣碎的事情。通常用於複雜的事情。能夠製作幾種方法並將它們分組在一個類中的能力對於使程式碼更易於閱讀非常有用。

您可以勾上

__new__

__init__

__call__

。這將允許您做不同的事情。即使通常您可以全部

__new__

使用它,有些人也更習慣使用

__init__

這些被稱為元類,該死!它一定意味著什麼!

為什麼要使用元類?

現在是個大問題。為什麼要使用一些晦澀的易錯功能?

好吧,通常您不會:

元類是更深層的魔術,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)142630324

一切都是 Python 中的物件,它們都是類的例項或元類的例項。

除了

type

type

實際上是它自己的元類。這不是您可以在純 Python 中複製的東西,而是透過在實現級別上作弊來完成的。

其次,元類很複雜。您可能不希望將它們用於非常簡單的類更改。您可以使用兩種不同的技術來更改類:

由於篇幅限制,就不一一展示了,有需要文中 python 面試題筆記完整版的朋友可私信@不禿頭的小鹿回覆“666”即可領取噢~