Python很簡單?學會魔術方法才算入門!
點選上方“中興開發者社群”,關注我們
每天讀一篇一線開發者原創好文
Python中的元類是什麼?
原問題地址:http://stackoverflow.com/questions/100003/what-is-a-metaclass-in-python
問題
什麼是元類?使用它們能做什麼?
答案 1
元類是類的一種。正如類定義了例項功能,元類也定義了類的功能。類是元類的例項。
在Python中你可以隨意呼叫元類(參考Jerub的回答),實際上更有用的方法是使其本身成為一個真正的類。type是Python中常用的元類。你可能覺得奇怪,是的,type本身就是一個類,而且它就是type型別。你無法在Python中重新創造出完全像type這樣的東西,但Python提供了一個小把戲。你只需要把type作為子類,就可以在Python中建立自己的元類。
元類是最常用的一類工廠。就像你通過呼叫類來建立類例項,Python也是通過呼叫元類來建立一個新類(當它執行class語句的時候),結合常規的 __init__
和__new__
方法,元類可以允許你在建立類的時候做一些“額外的事情”,like
registering the new class with some registry(暫時不知道這句話的含義,不知道怎麼翻譯,字面意思是:就像用某個登錄檔來註冊新的類那樣),甚至可以用別的東西完全代替已有的類。
當Python執行class語句時,它首先把整個的class語句作為一個正常的程式碼塊來執行。由此產生的名稱空間(一個字典)具有待定類的屬性。元類取決於待定類的基類(元類是具有繼承性的)、或待定類的__metaclass__
屬性(如果有的話)或__metaclass__
全域性變數。接下來,用類的名稱、基類和屬性呼叫元類,從而把元類例項化。
然而,元類實際上定義的一個類的型別,而不只是類工廠,所以你可以用元類來做更多的事情。例如,你可以定義元類的一般方法。這些元類方法和類方法有相似之處,因為它們可以被沒有例項化的類呼叫。但這些元類方法和類方法也有不同之處,元類方法不能在類的例項中被呼叫。type.__subclasses__()
是關於type的一個方法。你也可以定義常規的“魔法”函式,如__add__
, __iter__
和__getattr__
,以便實現或修改類的功能。
摘抄一個例子:
def make_hook(f):
"""Decorator to turn 'foo' method into '__foo__'"""
f.is_hook = 1
return f
class MyType(type):
def __new__(cls, name, bases, attrs):
if name.startswith('None'):
return None
# Go over attributes and see if they should be renamed.
newattrs = {}
for attrname, attrvalue in attrs.iteritems():
if getattr(attrvalue, 'is_hook', 0):
newattrs['__%s__' % attrname] = attrvalue
else:
newattrs[attrname] = attrvalue
return super(MyType, cls).__new__(cls, name, bases, newattrs)
def __init__(self, name, bases, attrs):
super(MyType, self).__init__(name, bases, attrs)
# classregistry.register(self, self.interfaces)
print "Would register class %s now." % self
def __add__(self, other):
class AutoClass(self, other):
pass
return AutoClass
# Alternatively, to autogenerate the classname as well as the class:
# return type(self.__name__ + other.__name__, (self, other), {})
def unregister(self):
# classregistry.unregister(self)
print "Would unregister class %s now." % self
class MyObject:
__metaclass__ = MyType
class NoneSample(MyObject):
pass
# Will print "NoneType None"
print type(NoneSample), repr(NoneSample)
class Example(MyObject):
def __init__(self, value):
self.value = value
@make_hook
def add(self, other):
return self.__class__(self.value + other.value)
# Will unregister the class
Example.unregister()
inst = Example(10)
# Will fail with an AttributeError
#inst.unregister()
print inst + inst
class Sibling(MyObject):
pass
ExampleSibling = Example + Sibling
# ExampleSibling is now a subclass of both Example and Sibling (with no
# content of its own) although it will believe it's called 'AutoClass'
print ExampleSibling
print ExampleSibling.__mro__
shareedit
答案 2
作為物件的類
在理解元類之前,你需要掌握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
...
這個物件(類)本身就能夠建立一些物件(例項),這就是為什麼它是類。
但它仍然是一個物件,因而:
-
你可以將它分配給一個變數
-
你可以複製它
-
你可以增加它的屬性
-
你可以把它作為一個功能引數來用
例如:
>>> print(ObjectCreator) # you can print a class because it's an object
<class '__main__.ObjectCreator'>
>>> def echo(o):
... print(o)
...
>>> echo(ObjectCreator) # you can pass a class as a parameter
<class '__main__.ObjectCreator'>
>>> 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>
Creating classes dynamically
類的動態建立
既然類是物件,你就能動態地建立它們,就像建立任何物件那樣。
首先,你可以在一個使用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
<class '__main__.Foo'>
>>> print(MyClass()) # you can create an object from this class
<__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是這樣應用的:
例如:
>>> class MyShinyClass(object):
... pass
可以這樣子來進行手動建立
>>> MyShinyClass = type('MyShinyClass', (), {}) # returns a class object
>>> print(MyShinyClass)
<class '__main__.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)
<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
你現在明白了:在Python中,類就是物件,你可以動態地建立類。
這就是關鍵字class在Python中的應用,它是通過使用元類來發揮作用。
元類是什麼
元類是用來建立類的東西。
你通過定義類來建立物件,對吧?
但我們知道Python的類就是物件。
元類用於建立這些物件,元類是類的類,你可以這樣來描述它們:
MyClass = MetaClass()
MyObject = MyClass()
你已經看到了type可以這樣來用:
MyClass = type('MyClass', (), {})
這是因為type實際上是一個元類,作為元類的type在Python中被用於在後臺建立所有的類。
現在你感到疑惑的是為什麼這裡是小寫的type,而不是大寫的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__
的屬性
當你建立類的時候,可以新增一個__metaclass__
屬性:
class Foo(object):
__metaclass__ = something...
[...]
如果你這樣做,Python會使用元類來建立Foo這個類。
小心,這是棘手的。
這是你是首次建立class
Foo(object)
,但是類物件Foo在記憶體中還沒有被建立。
Python會在類定義中尋找__metaclass__
。如果找到它,Python會用它來建立物件類Foo。如果沒有找到它,Python將使用type來建立這個類。
把上面的話讀幾遍。
當你寫下:
class Foo(Bar):
pass
Python會實現以下功能:
Foo有沒有__metaclass__
的屬性?
如果有,通過借鑑__metaclass__
,用Foo這個名字在記憶體中建立一個類物件(我說的是一個類物件,記住我的話)。
如果Python找不到__metaclass__
,它會在模組層級尋找__metaclass__
,並嘗試做同樣的事情(但這隻適用於不繼承任何東西的類,基本上是舊式類)。
如果它根本找不到任何__metaclass__
,它將使用Bar(第一個父類)自己的元類(這可能是預設的type)來建立類物件。
小心點,__metaclass__
屬性不會被繼承,而父類的元類(Bar.__class__
)將會被繼承。如果Bar
所用的__metaclass__
屬性是用type()
來建立Bar(而不是type.__new__()
),它的子類不會繼承這種功能。
現在最大的問題是,你可以在__metaclass__
中寫些什麼?
答案是:可以建立類的東西。
什麼可以建立類?type,或者父類。
自定義元類
一個元類的主要目的是當它被建立時,這個類可以自動改變。
通常在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_attr):
"""
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_attr = {}
for name, val in future_class_attr.items():
if not name.startswith('__'):
uppercase_attr[name.upper()] = val
else:
uppercase_attr[name] = val
# let `type` do the class creation
return type(future_class_name, future_class_parents, uppercase_attr)
__metaclass__ = upper_attr # this will affect all classes in the module
class 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'
print(hasattr(Foo, 'bar'))
# Out: False
print(hasattr(Foo, 'BAR'))
# Out: True
f = Foo()
print(f.BAR)
# Out: 'bip'
現在,讓我們完全照做,但使用一個真的類作為元類:
# remember that `type` is actually a class like `str` and `int`
# so you can inherit from it
class 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_attr):
uppercase_attr = {}
for name, val in future_class_attr.items():
if not name.startswith('__'):
uppercase_attr[name.upper()] = val
else:
uppercase_attr[name] = val
return type(future_class_name, future_class_parents, uppercase_attr)
但這不是真正的物件導向程式設計。我們直接呼叫type,我們無需覆蓋或呼叫父類__new__
。讓我們著手吧:
class UpperAttrMetaclass(type):
def __new__(upperattr_metaclass, future_class_name, future_class_parents, future_class_attr):
uppercase_attr = {}
for name, val in future_class_attr.items():
if not name.startswith('__'):
uppercase_attr[name.upper()] = val
else:
uppercase_attr[name] = val
# reuse the type.__new__ method
# this is basic OOP, nothing magic in there
return type.__new__(upperattr_metaclass, future_class_name, future_class_parents, uppercase_attr)
你可能已經注意到額外的引數upperattr_metaclass
,它沒有什麼特別之處:__new__
以upperattr_metaclass
為第一引數,並且在upperattr_metaclass
中被定義。就像你把self作為例項的第一個引數那樣,或者作為類方法的第一個引數。
當然,我在這裡為了清晰起見,使用了一個很長的名稱,但就像self一樣,所有的引數都有習慣的名稱。所以,在開發實踐中所寫的元類看起來是這樣的:
class UpperAttrMetaclass(type):
def __new__(cls, clsname, bases, dct):
uppercase_attr = {}
for name, val in dct.items():
if not name.startswith('__'):
uppercase_attr[name.upper()] = val
else:
uppercase_attr[name] = val
return type.__new__(cls, clsname, bases, uppercase_attr)
我們可以用super使它更清楚,這樣將緩解繼承(因為,是的,你可以擁有元類,繼承metaclasses
,繼承type):
class UpperAttrMetaclass(type):
def __new__(cls, clsname, bases, dct):
uppercase_attr = {}
for name, val in dct.items():
if not name.startswith('__'):
uppercase_attr[name.upper()] = val
else:
uppercase_attr[name] = val
return super(UpperAttrMetaclass, cls).__new__(cls, clsname, bases, uppercase_attr)
就是這樣。關於元類真的是沒有更多要講的了。
使用元類的程式碼複雜的原因並不在於元類,那是因為你通常用元類來實現一些奇怪的功能,而這些功能要依靠自省、繼承、變數,如:__dict__
等。
的確,元類對於“魔法”特別有用,這些事務是複雜的,但元類本身很簡單:
-
攔截類的建立
-
修改類
-
返回修改後的類
你為什麼要使用元類而不是函式?
__metaclass__
可以被任意呼叫。既然類明顯更復雜,你為什麼還要使用它呢?
這樣做有幾個理由:
-
目的明確。當你讀
UpperAttrMetaclass(type)
時,你知道要遵循的是什麼。 -
可以使用物件導向程式設計。元類可以繼承元類、重寫父類的方法。元類甚至可以使用元類。
-
可以優化程式碼。你從不為像上面例子中瑣碎的東西而使用元類。它通常用於複雜的東西。在一個類中有多種方法並且將它們優化組合,是非常有價值的,使得程式碼可讀性更強。
-
你可能喜歡使用
__new__
,__init__
和__call__
。它們能幫你實現不同的功能。儘管你通常可以用__new__
來實現所有的功能,有些人還是更喜歡使用__init__
。 -
這些被稱為元類。可惡!它肯定意味著什麼!
你為什麼會使用元類?
現在的大問題是:為什麼你會使用一些複雜難懂、容易出錯的特性?
嗯,通常你不會這樣做:
元類是更深層次的魔法,超過99%的使用者不需要擔心。不要懷疑你是否需要元類(那些真正需要元類的人對此確定無疑,並且不需要解釋為什麼)。
——Python專家蒂姆﹒彼得斯
元類的主要使用案例是建立API。一個典型的例子就是Django ORM。
它允許你定義類似這樣的東西:
class Person(models.Model):
name = models.CharField(max_length=30)
age = models.IntegerField()
但如果你這樣做:
guy = Person(name='bob', age='35')
print(guy.age)
它不會返回到一個IntegerField
物件。它會返回到一個int,甚至可以直接從資料庫讀取。
這是可以實現的,因為models.Model
定義了__metaclass__
。它使用魔法把你剛才用簡單語句定義的Person轉化成一個複雜的鉤子連線到資料庫欄位。
通過顯示一個簡單的API和元類,Django使複雜的東西看起來簡單,再從API重構程式碼去完成真正的幕後工作。
最後的話
首先,你知道,類是可以建立例項的物件。
事實上,類本身就是例項。在元類中
>>> class Foo(object): pass
>>> id(Foo)
142630324
在Python中,一切都是物件,它們都是類的例項或元類的例項。
但是type除外。
type實際上是自己的元類。你無法在純粹的Python中複製它,所以只能在實施層面做點小把戲。
其次,元類是複雜的。你可能不想把它們用於非常簡單的類。你可以用兩種不同的技術來改變類:
-
猴子補丁monkey patching
-
類裝飾器
當你需要改變類的時候,99%的情況下,使用它們是明智之舉。
但99%的時間,你根本不需要改變類。
轉自:https://github.com/qiwsir/StackOverFlowCn/blob/master/302.md
相關文章
- 學會這10種機器學習演算法,你才算入門(附教程)機器學習演算法
- 學習了哪些知識,計算機視覺才算入門?計算機視覺
- #魔術方法(會話管理)會話
- Python 魔術方法指南Python
- Python 魔術方法 - Magic MethodPython
- python中的魔術方法__Python
- python魔術方法詳解Python
- PHP中的魔術方法和魔術常量簡介和使用PHP
- 入門Flink,很簡單
- Python入門方法--簡單總結+學習方式思考Python
- 怎樣學python ,才不會從入門到放棄Python
- 為什麼機器學習會選擇Python語言?很簡單!機器學習Python
- Python 中的魔術方法詳解Python
- Python中的魔術方法詳解Python
- C語言入門很簡單pdfC語言
- PHP 魔術方法PHP
- PHP魔術方法PHP
- 想學AI開發很簡單:只要你會複製貼上AI
- php 魔術方法 __callPHP
- PHP魔術方法和魔術常量介紹及使用PHP
- PHP 魔術常量簡要PHP
- ajax入門 不要畏懼 很簡單 進了門一切都好學多了
- Python爬蟲入門並不難,甚至進階也很簡單,掌握了這些就簡單了Python爬蟲
- HTML很簡單?不!HTML
- 程式設計師數學之數學魔術人人皆會變程式設計師
- Python魔術方法 __getattr__、__getattribute__使用詳解Python
- Promise 其實很簡單Promise
- python 魔術方法 : 讓自定義類更像內建型別Python型別
- PHP 物件導向 (六)魔術方法PHP物件
- 繼承關係和魔術方法繼承
- CUDA 高效能平行計算入門
- 其實泛型很簡單泛型
- 誰說 JavaScript 很簡單了?JavaScript
- 複習下 git,很簡單Git
- 用 Python 的魔術方法做出更好的正規表示式 APIPythonAPI
- 詳解Python魔術方法__getitem__、__setitem__、__delitem__、__len__Python
- Python為什麼發展這麼快速?原因很簡單!Python
- 簡單的 Vue.js 入門方法Vue.js