<meta charset="utf-8">
Class也是Object
在理解metaclass之前屈溉,我們需要先理解Python中的class们镜。從某種程度上來說缴渊,Python中的class的定位比較特殊仑乌。
對于大部分面向對象語言來說兑燥,class是一段定義了如何產(chǎn)生object的代碼塊亮瓷。在Python中這一定義也成立:
>>> class example(object):
... pass
...
>>> object1 = example()
>>> print(object1)
<__main__.example object at 0x102e26990>
但是在Python中降瞳,class并不只有這一角色嘱支。class實際上也是object。當我們使用class定義一個類的時候,Python會執(zhí)行相應代碼并在內(nèi)存中創(chuàng)建一個名為example
的object除师。
但該object(class)是具有創(chuàng)建其他object(instance)的能力的赢织。這也是這個object是一個class的原因消返。由于本質上class任然是一個object狠毯,所以我們可以對class做出以下操作:
- 我們可以將其賦給一個變量
- 我們可以對其進行拷貝
- 我們可以賦給其新的變量
- 我們可以將其作為參數(shù)賦給其他的函數(shù)
舉例如下:
# print a class since it's an object
>>> print(example)
<class '__main__.example'>
# assign an attribute to the class
>>> print(hasattr(example, 'new_attribute'))
False
>>> example.new_attribute = 'assign an attribute to the class'
>>> print(hasattr(example, 'new_attribute'))
True
>>> print(example.new_attribute)
assign an attribute to the class
# assign the class to a variable
>>> example_mirror = example
>>> print(example_mirror)
<class '__main__.example'>
>>> print(example_mirror())
<__main__.example object at 0x102e26a90>
# pass class as a parameter
>>> def echo(cls):
... print(cls)
...
>>> echo(example)
<class '__main__.example'>
動態(tài)創(chuàng)建class
既然class也是object,那么我們就可以像創(chuàng)建普通的object一樣動態(tài)創(chuàng)建class亭引。
第一種方法贞岭,我們可以在方法中創(chuàng)建class八毯。如下面的例子所示:
>>> def dynamic_class_creater(name):
... if name == 'name1':
... class class1(object):
... pass
... return class1
... else:
... class class2(object):
... pass
... return class2
...
>>> first_class = dynamic_class_creater('name1')
>>> print(first_class)
<class '__main__.class1'>
>>> print(first_class())
<__main__.class1 object at 0x10e4149d0>
但通過這種方式創(chuàng)建class并沒有特別動態(tài)。我們?nèi)稳恍枰约憾x類的具體內(nèi)容瞄桨』八伲考慮到class也是object,那么也一定有某種方法能夠像產(chǎn)生instance一樣產(chǎn)生類芯侥。
當我們使用class關鍵字創(chuàng)建類的時候泊交,Python會自動創(chuàng)建對應的object。像Python中其他大多數(shù)情況一樣柱查,我們也可以手動創(chuàng)建這個class object廓俭。這一操作可以通過type()
實現(xiàn)。
通常情況下我們可以調用type
來得到一個object的類型是什么唉工。如下面的例子所示:
>>> print(type(1))
<type 'int'>
>>> print(type('str'))
<type 'str'>
>>> print(type(example()))
<class '__main__.example'>
>>> print(type(example))
<type 'type'>
在這里我們看到我們所創(chuàng)建example類的type是'type'
研乒。這實際上也就是接下來要討論的內(nèi)容。既type
的完全不同的功能——type
可以動態(tài)創(chuàng)建class淋硝。type()
函數(shù)可以接收class的描述來作為參數(shù)并返回所生成的class object雹熬。type
同時具有這兩個迥異的功能是由于Python兼容性問題導致的。在此我們不做深究谣膳。
當使用type
創(chuàng)建class時竿报,其用法如下:
type(class_name, tuple_of_parent_class, dict_of_attribute_names_and_values)
其中第二個參數(shù)tuple_of_parent_class
用來表示繼承關系,可以為空继谚。第三個參數(shù)用來描述我們所要創(chuàng)建的類所應該具有的attribute烈菌。如下面的例子所示:
>>>class class_example(object):
... pass
上面定義的這個類可以由如下type
函數(shù)創(chuàng)建:
>>>class_example = type('class_example', (), {}) # create a class on the fly
>>>print(class_example)
<class '__main__.class_example'>
>>> print(class_example()) # get a instance of the class
<__main__.class_example object at 0x10e414b10>
在這個例子中,type
所接收的第一個參數(shù)'class_example'
是該類的類名犬庇,同時我們使用了class_example
作為存儲該class object引用的變量僧界。這二者可以不同侨嘀。但一般我們沒有理由采用不同的名字從而使得代碼更加復雜臭挽。
我們也可以使用一個字典來定義所創(chuàng)建的class的attribute:
>>> class_example = type('class_example', (), {'attr': 1})
>>> print(class_example)
<class '__main__.class_example'>
>>> print(class_example.attr)
1
>>> print(class_example())
<__main__.class_example object at 0x10e414a90>
>>> print(class_example().attr)
1
上面的例子中type
返回的class等同于下面這個class:
>>> class class_example(object):
... attr = 1
當然,我們也可以用type返回一個繼承class_example
的類:
>>> child_example = type('child_example', (class_example,), {})
>>> print(child_example)
<class '__main__.child_example'>
>>> print(child_example.attr)
1
上面這個例子中type返回的class等同于如下class:
>>> class child_example(class_example):
... pass
我們甚至可以動態(tài)創(chuàng)建包括方法的類咬腕。只要我們創(chuàng)建好方法并將其賦給相應的attribute即可:
>>> def echo(self):
... print(self.attr)
...
>>> child_example = type('child_example', (class_example,), {'echo': echo})
>>> hasattr(class_example, 'echo')
False
>>> hasattr(child_example, 'echo')
True
>>> child_example().echo()
1
同樣欢峰,我們也可以先動態(tài)創(chuàng)建一個class,然后再賦給其新的方法:
>>> child_example = type('child_example', (class_example,), {})
>>> def another_method(self):
... print('another method')
...
>>> child_example.another_method = another_method
>>> hasattr(child_example, 'another_method')
True
>>> child_example().another_method()
another method
綜上所述,Python中的class其實是一個object纽帖,并且我們可以動態(tài)創(chuàng)建class宠漩。事實上這也是我們在使用class關鍵字的時候Python所做的事情。Python通過使用metacalss來實現(xiàn)這一過程懊直。
究竟什么是metaclass扒吁?
metaclass就是Python中用來創(chuàng)建class object的class。我們可以將其看做能夠產(chǎn)生class的類工廠室囊。我們可以通過如下例子理解這個關系:
class = metaclass()
object = class()
從上文中我們知道了type()
可以被用來動態(tài)創(chuàng)建class雕崩,這是因為實際上type
是一個metaclass。而且type
實際上是Python用在在幕后創(chuàng)建所有class的metaclass融撞。
包括int, string, function, class在內(nèi)盼铁,Python中所有的東西都是object,而所有的object都是被相應的class創(chuàng)造的尝偎。我們可以通過__class__
的值得知這一點饶火。
>>> age = 24
>>> age.__class__
<type 'int'>
>>> name = 'bob'
>>> name.__class__
<type 'str'>
>>> def foo(): pass
>>> foo.__class__
<type 'function'>
>>> class Bar(object): pass
>>> bar = Bar()
>>> bar.__class__
<class '__main__.Bar'>
那么,這些__class__
的__class__
又是什么呢致扯?
>>> age.__class__.__class__
<type 'type'>
>>> name.__class__.__class__
<type 'type'>
>>> foo.__class__.__class__
<type 'type'>
>>> bar.__class__.__class__
<type 'type'>
可以看出肤寝,所有的class都來自于type
。type
抖僵,作為metaclass醒陆,創(chuàng)建了以上所有的class object。
type
是Python定義好的metaclass裆针。當然刨摩,我們也可以自定義metaclass。
類的metaclass attribute
當定義class的時候世吨,我們可以使用__metaclass__
attribute來指定用來初始化當前class的metaclass澡刹。如下面的例子所示:
class Foo(object):
__metaclass__ = something
[other statements...]
如果我們指定了__metaclass__
,Python就是使用這個metaclass來生成class Foo
耘婚。
當Python試圖創(chuàng)建class Foo
的時候罢浇,Python會首先在class的定義中尋找__metaclass__
attribute。如果存在__metaclass__
沐祷,Python將會使用指定的__metaclass__
來創(chuàng)建class Foo
嚷闭。如果沒有指定的話,Python就會使用默認的type
作為metaclas創(chuàng)建Foo
赖临。
所以胞锰,對于下面這個例子:
class Foo(Bar):
pass
Python首先在Foo
中尋找是否存在__metaclass__
attribute。
如果存在的話兢榨,Python將使用這個metaclass在內(nèi)存中創(chuàng)建一個名字為Foo
的class object嗅榕。如果Python
如果class定義中不存在__metaclass__
的話顺饮,Python將會尋找MODULE級別的__metaclass__
。如果存在的話鳩進行與前述相同的操作凌那。但是只有我們定義的class沒有繼承任何類的情況下兼雄,Python才會在MODULE級別尋找__metaclass__
∶钡或者說赦肋,只有當該類是一個舊類的情況下,Python才會在MODULE級別尋找__metaclass__
励稳。(關于新類和舊類的區(qū)別金砍,請看這篇文章).
當Python仍然沒有找到__metaclass__
時,Python將會使用當前類的母類的metaclass來創(chuàng)建當前類麦锯。在我們上面這個例子中恕稠,Python會使用Foo
的母類Bar
的metaclass來創(chuàng)建Foo
的class object。
同時需要注意的是扶欣,在class中定義的__metaclass__
attribute并不會被子類繼承鹅巍。被子類繼承的是母類的metaclass,也就是母類的.__class__
attribute料祠。比如上面的例子中骆捧,Bar.__class__
將會被Foo
繼承。也就是說髓绽,如果Bar
定義了一個__metaclass__
attribute來使用type()
創(chuàng)建Bar
的class object(而非使用type.__new__()
)敛苇,那么Bar
的子類,也就是Foo
顺呕,并不會繼承這一行為枫攀。
那么問題來了:我們究竟應該在__metaclass__
attribute中定義什么?
答案是:能夠創(chuàng)建class的東西株茶。
那么什么能夠創(chuàng)建class呢来涨?type
,或者任何type
的子類启盛。
自定義metaclass
metaclass的主要目的是在class被創(chuàng)建的時候對生成的class進行自動的動態(tài)修改蹦掐。
一般來說,這一點主要應用于API僵闯,例如我們想要根據(jù)當前的內(nèi)容創(chuàng)建相匹配的class卧抗。
舉一個簡單的例子如下:我們決定讓當前module下所有的class的attribute的名字都是大寫。要實現(xiàn)這個功能有很多種方法鳖粟。使用__metaclass__
就是其中之一社裆。
設置了__metaclass__
的話,class的創(chuàng)建就會由指定的metaclass處理牺弹,那么我們只需要讓這個metaclass將所有attribute的名字改成大寫即可浦马。
__metaclass__
可以是任何Python的callable
时呀,不必一定是一個正式的class张漂。
下面我們首先給出一個使用function作為__metaclass__
的例子晶默。
# the metaclass will automatically get passed the same argument
# that is passed to `type()`
def upper_attr(class_name, class_parents, class_attr):
'''Return a class object, with the list of its attribute turned into
uppercase.
'''
# pick up any attribute that doesn't start with '__' and turn it into uppercase.
uppercase_attr = {}
for name, val in class_attr.items():
if name.startswith('__'):
uppercase_attr[name] = val
else:
uppercase_attr[name.upper()] = val
# let `type` do the class creation
return type(class_name, class_parents, uppercase_attr)
class Foo(object):
# this __metaclass__ will affect the creation of this new style class
__metaclass__ = upper_attr
bar = 'bar'
print(hasattr(Foo), 'bar')
# False
print(hasattr(Foo), 'BAR')
# True
f = Foo()
print(f.BAR)
# 'bar'
接下來我們通過繼承type的方式實現(xiàn)一個真正的class形式的metaclass。注意如果尚不清楚__new__
和__init__
的作用和區(qū)別的航攒,請看這篇文章.
# remember that `type` is actually a just a class like int or str
# so we can inherit from it.
class UpperAttrMetaclass(type):
'''
__new__ is the method called before __init__
It's the function that actually creates the object and returns it.
__init__ only initialize the object passed as a parameter.
We rarely use __new__, except when we want to control how the object
is created.
For a metaclass, the object created is a class. And since we want to
customize it, we need to override __new__.
We can also do something by overriding __init__ to get customized initialization
process as well.
Advanced usage involves override __call__, but we won't talk about this here.
'''
def __new__(upperattr_metaclass, class_name, class_parents, class_attr):
uppercase_attr = {}
for name, val in class_attr.items():
if name.startswith('__'):
uppercase_attr[name] = val
else:
uppercase_attr[name.upper()] = val
return type(class_name, class_parents, uppercase_attr)
但這不是很OOP磺陡。我們直接調用了type
而非調用type.__new__
。那么OOP的做法如下漠畜。
class UpperAttrMetaclass(type):
def __new__(upperattr_metaclass, class_name, class_parents, class_attr):
uppercase_attr = {}
for name, val in class_attr.items():
if name.startswith('__'):
uppercase_attr[name] = val
else:
uppercase_attr[name.upper()] = val
# basic OOP. Reuse the parent's `__new__()`
return type.__new__(upperattr_metaclass, class_name, class_parents, uppercase_attr)
我們注意到币他,__new__
所接收的參數(shù)中有一個額外的upperattr_metaclass
。這沒有什么特別的憔狞。如同__init__
總是接收調用它的object作為第一個參數(shù)一樣(慣例上用self
來命名__init__
所接收的第一個參數(shù))蝴悉,__new__
總是接收其被定義在內(nèi)的class作為第一個參數(shù),就像類方法總是接收其被定義的class作為第一個參數(shù)一樣(慣例上用cls
命名類方法所接收的第一個參數(shù))瘾敢。
清楚起見拍冠,這里給出的例子的變量和方法名都很長。但在實際的應用中簇抵,類似于使用self
和cls
代替第一個參數(shù)庆杜,我們可以將這些名字替換為更加簡潔的形式:
class UpperAttrMetaclass(type):
def __new__(cls, cls_name, bases, attr_dict):
uppercase_attr = {}
for name, val in attr_dict.items():
if name.startswith('__'):
uppercase_attr[name] = val
else:
uppercase_attr[name.upper()] = val
return type.__new__(cls, cls_name, bases, uppercase_attr)
通過應用super
,我們可以使得上面這段代碼更加干凈簡潔碟摆,也使得繼承更加容易(我們可能有metaclass繼承別的一些metaclass晃财,而這些metaclass又繼承type
):
class UpperAttrMetaclass(type):
def __new__(cls, cls_name, bases, attr_dict):
uppercase_attr = {}
for name, val in attr_dict.items():
if name.startswith('__'):
uppercase_attr[name] = val
else:
uppercase_attr[name.upper()] = val
return super(UpperAttrMetaclass, cls).__new__(cls, cls_name, bases, uppercase_attr)
Voilà!上述基本就是關于metaclass的一切了典蜕。
使用metaclass之所以復雜断盛,不是因為其代碼實現(xiàn)復雜,而是因為我們一般使用metaclass來做一些邏輯上很復雜的操作愉舔,例如自省郑临,修改繼承以及改變類的默認attribute如__dict__
等。
metaclass的確可以被用來實現(xiàn)一些奇妙的功能屑宠,也因此可以用來進行極其復雜的邏輯操作厢洞。但是metaclass本身是很簡單的:
- 影響class初始化的過程
- 修改class的內(nèi)容
- 返回修改過的class
為什么我們要使用metaclass,而不是使用一些函數(shù)來實現(xiàn)類似的功能典奉?
就像前文所說躺翻,__metaclass__
實際上可以是任何callable
,那么為什么我們還要使用metaclass而不是直接調用這些函數(shù)呢卫玖?
使用class作為metaclass有如下幾個理由:
- 使用class作為metaclass能夠使得我們代碼的動機更加明確公你。比如當我們讀到上面所定義的
UpperAttrMetaclass(type)
代碼時,我們清楚地知道接下來這段代碼想要干什么(自定義class object初始化的過程)假瞬。 - 我們能夠使用OOP的思想進行處理陕靠。class作為metaclass可以繼承其他的metaclass迂尝,重載母類的方法,甚至可以使用別的metaclass剪芥。
- 如果我們使用class作為metaclass垄开,某一使用該metaclass的class的子類將仍是是其metaclass的實例。但這一功能無法通過使用函數(shù)作為metaclass實現(xiàn)税肪。
- 使用metaclass可以使得代碼結構更加優(yōu)美溉躲。實際應用中我們很少使用metaclass來實現(xiàn)上面那樣簡單的功能。使用metaclass往往是為了實現(xiàn)非常復雜的操作益兄。如果使用class作為metaclass锻梳,我們就可以把相應的方法封裝到這一個metaclass中,使得代碼更加易懂净捅。
- 使用class作為metaclass可以在class中容易的定義
__new__
疑枯,__init__
,__call__
方法蛔六。雖然我們在將所有的邏輯都放入__new__
中荆永,但有的時候根據(jù)需要使用其他幾個方法會使得邏輯更加清晰。 - 額賊古今!人家名字就叫metaclass屁魏。這不是帶著個class嗎?
為什么我們要使用metaclass呢捉腥?
那么究竟為什么我們要使用metaclass這樣一個難以理解且容易出錯的實現(xiàn)方式呢氓拼?
答案是通常情況下我們不需要使用metaclass。
引用Python大師Tim Peters的話來說抵碟,就是:
Metaclasses are deeper magic that 99% of users should never worry about. If you wonder whether you need them, you don't (the people who actually need them know with certainty that they need them, and don't need an explanation about why).
metaclass主要的使用情況就是用來創(chuàng)建API桃漾。使用metaclass的一個典型的例子是Django ORM。
它是的我們可以使用如下當時定義一個model:
class Person(models.Model):
name = models.CharField(max_length=30)
age = models.IntegerField()
同時拟逮,如果我們調用這個model:
guy = Person(name='bob', age='35')
print(guy.age)
其并不會返回一個IntegerField
對象撬统,而是會返回一個int
,甚至可以直接從數(shù)據(jù)庫中調用這個值敦迄。
正是因為models.Model
定義了__metaclass__
恋追,并使用了一些操作來將我們使用簡單的語句定義的Person
轉化成了與數(shù)據(jù)庫相應的域相聯(lián)系的類,這種邏輯才成為可能罚屋。
Django使得很多復雜的邏輯僅暴露一個簡單的API接口就可以調用苦囱,這正是通過metaclass實現(xiàn)的。metaclass會根據(jù)需要重新實現(xiàn)這些復雜操作所需要的真正的代碼脾猛。
再說兩句
首先我們知道了Python中的class實際上是object撕彤,同時class仍具有創(chuàng)建對應的實例的能力。
實際上class本身也是metaclass的實例猛拴。
>>> class Foo(object):
... pass
...
>>> id(Foo)
4299321816
Python中的任何東西都是object羹铅,這些object不是class的實例就是metaclass的實例蚀狰。
當然,type
除外职员。
type
事實上是其自身的metaclass麻蹋。我們使用Python是無法重復這種實現(xiàn)的。這一邏輯是在Python代碼實現(xiàn)的層面定義的廉邑。引用一下道德經(jīng)中的說法哥蔚,我們可以說Python中type生metaclass倒谷,metaclass生class蛛蒙,class生萬物。
另外渤愁,metaclass的應用一般頗為復雜牵祟,大多數(shù)情況下我們可以使用別的方法實現(xiàn)相同的功能。比如我們可以通過一下兩種技術修改class:
- monkey patching
- class decorators
99%我們需要改變class的情況下抖格,我們使用上述兩種技術可以解決诺苹。
但事實是,99%的情況下我們根本不需要改變class雹拄。
作者:耀凱考前突擊大師
鏈接:http://www.reibang.com/p/224ffcb8e73e
來源:簡書
著作權歸作者所有收奔。商業(yè)轉載請聯(lián)系作者獲得授權,非商業(yè)轉載請注明出處滓玖。