程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式師世界 >> 編程語言 >  >> 更多編程語言 >> Python

Python元類詳解

編輯:Python

新式類

新式類統一了類和類型,如果obj是新式類的實例,則type(obj)等同於obj.__class__

>>> class Foo:
... pass
>>> obj = Foo()
>>> obj.__class__
<class '__main__.Foo'>
>>> type(obj)
<class '__main__.Foo'>
>>> obj.__class__ is type(obj)
True
>>> n = 5
>>> d = {
 'x' : 1, 'y' : 2 }
>>> class Foo:
... pass
...
>>> x = Foo()
>>> for obj in (n, d, x):
... print(type(obj) is obj.__class__)
...
True
True
True

Type and Class

在Python 3中,所有類都是新式類。因此,在Python 3中,可以互換地引用對象的類型及其類是合理的。

注意:在Python 2中,默認情況下類是舊式的。在Python 2.2之前,根本不支持新式類。從Python 2.2開始,它們可以創建,但必須顯式聲明為new-style。

請記住,在Python中,一切都是對象。類也是對象。因此,類必須具有類型。class的類型是什麼?

考慮以下:

>>> class Foo:
... pass
...
>>> x = Foo()
>>> type(x)
<class '__main__.Foo'>
>>> type(Foo)
<class 'type'

該類型(type)x是class類Foo,如你所願。但是Foo,類本身的類型是type。通常,任何新式類的類型都是type。

您熟悉的內置類的類型還包括type:

>>> for t in int, float, dict, list, tuple:
... print(type(t))
...
<class 'type'>
<class 'type'>
<class 'type'>
<class 'type'>
<class 'type'>

對於這個問題,類型type是type也(是對的):

>>> type (type )
<class'type'>

type是一個元類,其中的類是實例。就像一個普通的對象是一個類的實例一樣,Python中的任何新式類,以及Python 3中的任何類,都是type元類的一個實例。

在上述情況中:

  • x是一個類的實例Foo。
  • Foo是type元類的一個實例。
  • type也是type元類的一個實例,因此它本身就是一個實例。

動態定義類

type()當傳遞一個參數時,內置函數返回一個對象的類型。對於新式類,通常與對象的__class__屬性相同:

>>> type(3)
<class 'int'>
>>> type(['foo', 'bar', 'baz'])
<class 'list'>
>>> t = (1, 2, 3, 4, 5)
>>> type(t)
<class 'tuple'>
>>> class Foo:
... pass
...
>>> type(Foo())
<class '__main__.Foo'>

你也可以type()用三個參數調用 - type(<name>, <bases>, <dct>)

  • <name>指定類名。這成為__name__類的屬性。
  • <bases>指定類繼承的基類的元組。這成為__bases__類的屬性。
  • <dct>指定包含類主體定義的命名空間字典。這成為__dict__類的屬性。

type()以這種方式調用會創建type元類的新實例。換句話說,它動態創建一個新類。

在以下每個示例中,頂部代碼段動態定義了一個類type(),而下面的代碼段使用該class語句通常的方式定義了類。在每種情況下,這兩個片段在功能上是等效的。
例1

在第一個例子中,<bases><dct>通過參數type()都是空的。沒有指定任何父類的繼承,並且最初沒有任何內容放在命名空間字典中。這是最簡單的類定義:

>>> Foo = type('Foo', (), {
})
>>> x = Foo()
>>> x
<__main__.Foo object at 0x04CFAD50>
>>> class Foo:
... pass
...
>>> x = Foo()
>>> x
<__main__.Foo object at 0x0370AD50>

例2

<bases>是一個帶有單個元素的元組Foo,指定Bar從中繼承的父類。屬性, attr最初放在命名空間字典中:

>>> Bar = type('Bar', (Foo,), dict(attr=100))
>>> x = Bar()
>>> x.attr
100
>>> x.__class__
<class '__main__.Bar'>
>>> x.__class__.__bases__
(<class '__main__.Foo'>,)
>>> class Bar(Foo):
... attr = 100
...
>>> x = Bar()
>>> x.attr
100
>>> x.__class__
<class '__main__.Bar'>
>>> x.__class__.__bases__
(<class '__main__.Foo'>,)

例3

這一次,又<bases>是空的。通過<dct>參數將兩個對象放入命名空間字典中。第一個是名為的屬性attr,第二個是名為的函數attr_val,它成為已定義類的方法:

>>> Foo = type(
... 'Foo',
... (),
... {

... 'attr': 100,
... 'attr_val': lambda x : x.attr
... }
... )
>>> x = Foo()
>>> x.attr
100
>>> x.attr_val()
100
>>> class Foo:
... attr = 100
... def attr_val(self):
... return self.attr
...
>>> x = Foo()
>>> x.attr
100
>>> x.attr_val()
100

例4

lambda在Python中只能定義非常簡單的函數。在下面的示例中,外部定義稍微復雜的函數,然後attr_val通過名稱在名稱空間字典中分配f:

>>> def f(obj):
... print('attr =', obj.attr)
...
>>> Foo = type(
... 'Foo',
... (),
... {

... 'attr': 100,
... 'attr_val': f
... }
... )
>>> x = Foo()
>>> x.attr
100
>>> x.attr_val()
attr = 100
>>> def f(obj):
... print('attr =', obj.attr)
...
>>> class Foo:
... attr = 100
... attr_val = f
...
>>> x = Foo()
>>> x.attr
100
>>> x.attr_val()
attr = 100

自定義元類

再次考慮這個陳舊的例子:

class Foo:
… pass

f = Foo()

該表達式Foo()創建了一個新的類實例Foo。解釋器遇到時Foo(),會發生以下情況:

調用Foo的父類方法__call__()。由於Foo是標准的新式類,它的父類是type元類,這樣type的__call__()方法被調用。

__call__()方法又調用以下內容:

  • __new__()
  • __init__()

如果Foo沒有定義__new__()__init__(),默認的方法是繼承Foo的祖先。但是如果Foo定義了這些方法,它們會覆蓋來自祖先的方法,這允許在實例化時進行自定義行為Foo。

在下面,定義了一個自定義方法,並將new()其指定為以下__new__()方法Foo:

>>> def new(cls):
... x = object.__new__(cls)
... x.attr = 100
... return x
...
>>> Foo.__new__ = new
>>> f = Foo()
>>> f.attr
100
>>> g = Foo()
>>> g.attr
100

這會修改類的實例化行為Foo:每次Foo創建一個實例時,默認情況下會使用一個名為的attr屬性對其進行初始化,該屬性attr的值為100。(這樣的代碼通常會出現在__init__()方法中,而不是典型的__new__()。這個例子是出於演示目的而設計的。)

現在,正如已經重申的那樣,類也是對象。假設您在創建類時喜歡類似地自定義實例化行為Foo。如果您要遵循上面的模式,您將再次定義一個自定義方法,並將其指定__new__()為類Foo的實例的方法。Foo是type元類的一個實例,所以代碼看起來像這樣:

# Spoiler alert: This doesn't work!
>>> def new(cls):
... x = type.__new__(cls)
... x.attr = 100
... return x
...
>>> type.__new__ = new
Traceback (most recent call last):
File "<pyshell#77>", line 1, in <module>
type.__new__ = new
TypeError: can't set attributes of built-in/extension type 'type'

除此之外,正如您所看到的,您無法重新分配type元類的__new__()方法。Python不允許。

這可能也是一樣。type是從中派生所有新樣式類的元類。無論如何,你真的不應該亂用它。但是,如果你想自定義一個類的實例化,那麼還有什麼辦法呢?

一種可能的解決方案是自定義元類。從本質上講,type您可以定義自己的元類,而不是使用元類,但您可以type使用它來進行修改。

第一步是定義一個元類,派生自type,如下:

>>> class Meta(type):
... def __new__(cls, name, bases, dct):
... x = super().__new__(cls, name, bases, dct)
... x.attr = 100
... return x
...

定義標頭class Meta(type):指定Meta派生自type。既然type是元類,那也是一個Meta元類。

請注意,__new__()已定義自定義方法Meta。type直接對元類進行這樣的操作是不可能的。該__new__()方法執行以下操作:

  • 經由代表super()的__new__()父元類的方法(type)實際創建一個新的類
  • 將自定義屬性分配attr給類,值為100
  • 返回新創建的類

現在是另一半:定義一個新類Foo並指定其元類是自定義元類Meta,而不是標准元類type。這是使用metaclass類定義中的關鍵字完成的,如下所示:

>>> class Foo(metaclass=Meta):
... pass
...
>>> Foo.attr
100

瞧! Foo已經自動從Meta元類獲取屬性attr了。當然,您定義的任何其他類也會這樣做:

>>> class Bar(metaclass=Meta):
... pass
...
>>> class Qux(metaclass=Meta):
... pass
...
>>> Bar.attr, Qux.attr
(100, 100)

與類作為創建對象的模板的方式相同,元類用作創建類的模板。元類有時被稱為類工廠。

比較以下兩個例子:

對象工廠:

>>> class Foo:
... def __init__(self):
... self.attr = 100
...
>>> x = Foo()
>>> x.attr
100
>>> y = Foo()
>>> y.attr
100
>>> z = Foo()
>>> z.attr
100

類工廠:

>>> class Meta(type):
... def __init__(
... cls, name, bases, dct
... ):
... cls.attr = 100
...
>>> class X(metaclass=Meta):
... pass
...
>>> X.attr
100
>>> class Y(metaclass=Meta):
... pass
...
>>> Y.attr
100
>>> class Z(metaclass=Meta):
... pass
...
>>> Z.attr
100

這真的有必要嗎?

就像上面的類工廠示例一樣簡單,它是元類工作方式的本質。它們允許自定義類實例化。

盡管如此,為了attr在每個新創建的類上賦予自定義屬性,這仍然是一件大驚小怪的事情。你真的需要一個元類嗎?

在Python中,至少有幾種方法可以有效地完成同樣的事情:

簡單繼承:

''' 學習中遇到問題沒人解答?小編創建了一個Python學習交流群:711312441 尋找有志同道合的小伙伴,互幫互助,群裡還有不錯的視頻學習教程和PDF電子書! '''
>>> class Base:
... attr = 100
...
>>> class X(Base):
... pass
...
>>> class Y(Base):
... pass
...
>>> class Z(Base):
... pass
...
>>> X.attr
100
>>> Y.attr
100
>>> Z.attr
100

類裝飾器

>>> def decorator(cls):
... class NewClass(cls):
... attr = 100
... return NewClass
...
>>> @decorator
... class X:
... pass
...
>>> @decorator
... class Y:
... pass
...
>>> @decorator
... class Z:
... pass
...
>>> X.attr
100
>>> Y.attr
100
>>> Z.attr
100

結論

元類很容易轉向成為“尋找問題的解決方案”的領域。通常不需要創建自定義元類。如果手頭的問題可以用更簡單的方式解決,那麼它應該是。盡管如此,理解元類是有益的,這樣你就可以理解Python類,並且可以識別元類真正適合使用的工具。


  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式師世界 All Rights Reserved