新式類統一了類和類型,如果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
在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元類的一個實例。
在上述情況中:
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_va
l,它成為已定義類的方法:
>>> 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__()
方法執行以下操作:
__new__()
父元類的方法(type)實際創建一個新的類現在是另一半:定義一個新類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類,並且可以識別元類真正適合使用的工具。