国产片侵犯亲女视频播放_亚洲精品二区_在线免费国产视频_欧美精品一区二区三区在线_少妇久久久_在线观看av不卡

腳本之家,腳本語言編程技術(shù)及教程分享平臺!
分類導航

Python|VBS|Ruby|Lua|perl|VBA|Golang|PowerShell|Erlang|autoit|Dos|bat|

服務(wù)器之家 - 腳本之家 - Python - Python中的Classes和Metaclasses詳解

Python中的Classes和Metaclasses詳解

2020-05-28 10:06Sahand Saba Python

這篇文章主要介紹了Python中的Classes和Metaclasses詳解,屬于基礎(chǔ)知識中類與對象的概念部分的深入,需要的朋友可以參考下

類和對象

類和函數(shù)一樣都是Python中的對象。當一個類定義完成之后,Python將創(chuàng)建一個“類對象”并將其賦值給一個同名變量。類是type類型的對象(是不是有點拗口?)。

類對象是可調(diào)用的(callable,實現(xiàn)了 __call__方法),并且調(diào)用它能夠創(chuàng)建類的對象。你可以將類當做其他對象那么處理。例如,你能夠給它們的屬性賦值,你能夠?qū)⑺鼈冑x值給一個變量,你可以在任何可調(diào)用對象能夠用的地方使用它們,比如在一個map中。事實上當你在使用map(str, [1,2,3])的時候,是將一個整數(shù)類型的list轉(zhuǎn)換為字符串類型的list,因為str是一個類。可以看看下面的代碼:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
>>> class C(object):
...   def __init__(self, s):
...       print s
...
>>> myclass = C
>>> type(C)
<type 'type'>
>>> type(myclass)
<type 'type'>
>>> myclass(2)
2
<__main__.C object at 0x10e2bea50>
>>> map(myclass, [1,2,3])
1
2
3
[<__main__.C object at 0x10e2be9d0>, <__main__.C object at 0x10e2bead0>, <__main__.C object at 0x10e2beb10>]
>>> map(C, [1,2,3])
1
2
3
[<__main__.C object at 0x10e2be950>, <__main__.C object at 0x10e2beb50>, <__main__.C object at 0x10e2beb90>]
>>> C.test_attribute = True
>>> myclass.test_attribute
True

正因如此,Python中的“class”關(guān)鍵字不像其他語言(例如C++)那樣必須出現(xiàn)在代碼main scope中。在Python中,它能夠在一個函數(shù)中嵌套出現(xiàn),舉個例子,我們能夠這樣在函數(shù)運行的過程中動態(tài)的創(chuàng)建類。看代碼:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
>>> def make_class(class_name):
...   class C(object):
...       def print_class_name(self):
...           print class_name
...   C.__name__ = class_name
...   return C
...
>>> C1, C2 = map(make_class, ["C1", "C2"])
>>> c1, c2 = C1(), C2()
>>> c1.print_class_name()
C1
>>> c2.print_class_name()
C2
>>> type(c1)
<class '__main__.C1'>
>>> type(c2)
<class '__main__.C2'>
>>> c1.print_class_name.__closure__
(<cell at 0x10ab6dbe8: str object at 0x10ab71530>,)

請注意,在這里通過make_class創(chuàng)建的兩個類是不同的對象,因此通過它們創(chuàng)建的對象就不屬于同一個類型。正如我們在裝飾器中做的那樣,我們在類被創(chuàng)建之后手動設(shè)置了類名。同樣也請注意所創(chuàng)建類的print_class_name方法在一個closure cell中捕捉到了類的closure和class_name。如果你對closure的概念還不是很清楚,那么最好去看看前篇,復習一下closures和decorators相關(guān)的內(nèi)容。
Metaclasses

如果類是能夠制造對象的對象,那制造類的對象又該叫做什么呢(相信我,這并不是一個先有雞還是先有蛋的問題)?答案是元類(Metaclasses)。大部分常見的基礎(chǔ)元類都是type。當輸入一個參數(shù)時,type將簡單的返回輸入對象的類型,這就不涉及元類。然而當輸入三個參數(shù)時,type將扮演元類的角色,基于輸入?yún)?shù)創(chuàng)建一個類并返回。輸入?yún)?shù)相當簡單:類名,父類及其參數(shù)的字典。后面兩者可以為空,來看一個例子:
 

?
1
2
3
4
5
6
>>> MyClass = type("MyClass", (object,), {"my_attribute": 0})
>>> type(MyClass)
<type 'type'>
>>> o = MyClass()
>>> o.my_attribute
0

特別注意第二個參數(shù)是一個tuple(語法看起來很奇怪,以逗號結(jié)尾)。如果你需要在類中安排一個方法,那么創(chuàng)建一個函數(shù)并且將其以屬性的方式傳遞作為第三個參數(shù),像這樣:
 

?
1
2
3
4
5
6
7
8
9
>>> def myclass_init(self, my_attr):
...   self.my_attribute = my_attr
...
>>> MyClass = type("MyClass", (object,), {"my_attribute": 0, "__init__": myclass_init})
>>> o = MyClass("Test")
>>> o.my_attribute
'Test'
>>> o.__init__
<bound method MyClass.myclass_init of <__main__.MyClass object at 0x10ab72150>>

我們可以通過一個可調(diào)用對象(函數(shù)或是類)來自定義元類,這個對象需要三個輸入?yún)?shù)并返回一個對象。這樣一個元類在一個類上實現(xiàn)只要定義了它的__metaclass__屬性。第一個例子,讓我們做一些有趣的事情看看我們能夠用元類做些什么:
 

?
1
2
3
4
5
6
7
8
9
10
>>> def mymetaclass(name, parents, attributes):
...   return "Hello"
...
>>> class C(object):
...   __metaclass__ = mymetaclass
...
>>> print C
Hello
>>> type(C)
<type 'str'>

請注意以上的代碼,C只是簡單地將一個變量引用指向了字符串“Hello”。當然了,沒人會在實際中寫這樣的代碼,這只是為了演示元類的用法而舉的一個簡單例子。接下來我們來做一些更有用的操作。在本系列的第二部分我們曾看到如何使用裝飾器類來記錄目標類每個方法的輸出,現(xiàn)在我們來做同樣的事情,不過這一次我們使用元類。我們借用之前的裝飾器定義:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
def log_everything_metaclass(class_name, parents, attributes):
  print "Creating class", class_name
  myattributes = {}
  for name, attr in attributes.items():
    myattributes[name] = attr
    if hasattr(attr, '__call__'):
      myattributes[name] = logged("%b %d %Y - %H:%M:%S",
                    class_name + ".")(attr)
  return type(class_name, parents, myattributes)
 
class C(object):
  __metaclass__ = log_everything_metaclass
 
  def __init__(self, x):
    self.x = x
 
  def print_x(self):
    print self.x
 
# Usage:
print "Starting object creation"
c = C("Test")
c.print_x()
 
# Output:
Creating class C
Starting object creation
- Running 'C.__init__' on Aug 05 2013 - 13:50:58
- Finished 'C.__init__', execution time = 0.000s
- Running 'C.print_x' on Aug 05 2013 - 13:50:58
Test
- Finished 'C.print_x', execution time = 0.000s

如你所見,類裝飾器與元類有著很多共同點。事實上,任何能夠用類裝飾器完成的功能都能夠用元類來實現(xiàn)。類裝飾器有著很簡單的語法結(jié)構(gòu)易于閱讀,所以提倡使用。但就元類而言,它能夠做的更多,因為它在類被創(chuàng)建之前就運行了,而類裝飾器則是在類創(chuàng)建之后才運行的。記住這點,讓我們來同時運行一下兩者,請注意運行的先后順序:
 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def my_metaclass(class_name, parents, attributes):
  print "In metaclass, creating the class."
  return type(class_name, parents, attributes)
 
def my_class_decorator(class_):
  print "In decorator, chance to modify the class."
  return class_
 
@my_class_decorator
class C(object):
  __metaclass__ = my_metaclass
 
  def __init__(self):
    print "Creating object."
 
c = C()
 
# Output:
In metaclass, creating the class.
In decorator, chance to modify the class.
Creating object.

元類的一個實際用例

讓我們來考慮一個更有用的實例。假設(shè)我們正在構(gòu)思一個類集合來處理MP3音樂文件中使用到的ID3v2標簽Wikipedia。簡而言之,標簽由幀(frames)組成,而每幀通過一個四字符的識別碼(identifier)進行標記。舉個例子,TOPE標識了原作者幀,TOAL標識了原專輯名稱等。如果我們希望為每個幀類型寫一個單獨的類,并且允許ID3v2標簽庫用戶自定義他們自己的幀類。那么我們可以使用元類來實現(xiàn)一個類工廠模式,具體實現(xiàn)方式可以這樣:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
frametype_class_dict = {}
 
class ID3v2FrameClassFactory(object):
  def __new__(cls, class_name, parents, attributes):
    print "Creating class", class_name
    # Here we could add some helper methods or attributes to c
    c = type(class_name, parents, attributes)
    if attributes['frame_identifier']:
      frametype_class_dict[attributes['frame_identifier']] = c
    return c
 
  @staticmethod
  def get_class_from_frame_identifier(frame_identifier):
    return frametype_class_dict.get(frame_identifier)
 
class ID3v2Frame(object):
  frame_identifier = None
  __metaclass__ = ID3v2FrameClassFactory
  pass
 
class ID3v2TitleFrame(ID3v2Frame):
  __metaclass__ = ID3v2FrameClassFactory
  frame_identifier = "TIT2"
 
class ID3v2CommentFrame(ID3v2Frame):
  __metaclass__ = ID3v2FrameClassFactory
  frame_identifier = "COMM"
 
title_class = ID3v2FrameClassFactory.get_class_from_frame_identifier('TIT2')
comment_class = ID3v2FrameClassFactory.get_class_from_frame_identifier('COMM')
print title_class
print comment_class
 
# Output:
Creating class ID3v2Frame
Creating class ID3v2TitleFrame
Creating class ID3v2CommentFrame
<class '__main__.ID3v2TitleFrame'>
<class '__main__.ID3v2CommentFrame'>

當然了,以上的代碼同樣可以用類裝飾器來完成,以下是對應代碼:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
frametype_class_dict = {}
 
class ID3v2FrameClass(object):
  def __init__(self, frame_id):
    self.frame_id = frame_id
 
  def __call__(self, cls):
    print "Decorating class", cls.__name__
    # Here we could add some helper methods or attributes to c
    if self.frame_id:
      frametype_class_dict[self.frame_id] = cls
    return cls
 
  @staticmethod
  def get_class_from_frame_identifier(frame_identifier):
    return frametype_class_dict.get(frame_identifier)
 
@ID3v2FrameClass(None)
class ID3v2Frame(object):
  pass
 
@ID3v2FrameClass("TIT2")
class ID3v2TitleFrame(ID3v2Frame):
  pass
 
@ID3v2FrameClass("COMM")
class ID3v2CommentFrame(ID3v2Frame):
  pass
 
title_class = ID3v2FrameClass.get_class_from_frame_identifier('TIT2')
comment_class = ID3v2FrameClass.get_class_from_frame_identifier('COMM')
print title_class
print comment_class
 
Decorating class ID3v2Frame
Decorating class ID3v2TitleFrame
Decorating class ID3v2CommentFrame
<class '__main__.ID3v2TitleFrame'>
<class '__main__.ID3v2CommentFrame'>

如你所見,我們可以直接給裝飾器傳遞參數(shù),而元類卻不能。給元類傳遞參數(shù)必須通過屬性。正因如此,這里裝飾器的解決方案更為清晰,同時也更容易維護。然而,同時也需要注意當裝飾器被調(diào)用的時候,類已經(jīng)建立完畢,這意味著此時就不能夠修改其屬性了。例如,一旦類建立完成,你就不能夠修改__doc__。來看實際例子:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
>>> def mydecorator(cls):
...   cls.__doc__ = "Test!"
...   return cls
...
>>> @mydecorator
... class C(object):
...   """Docstring to be replaced with Test!"""
...   pass
...
Traceback (most recent call last):
 File "<stdin>", line 2, in <module>
 File "<stdin>", line 2, in mydecorator
AttributeError: attribute '__doc__' of 'type' objects is not writable
>>> def mymetaclass(cls, parents, attrs):
...   attrs['__doc__'] = 'Test!'
...   return type(cls, parents, attrs)
...
>>> class D(object):
...   """Docstring to be replaced with Test!"""
...   __metaclass__ = mymetaclass
...
>>> D.__doc__
'Test!'

通過type生成元類

正如我們所說,最基本的元類就是type并且類通常都是type類型。那么問題很自然來了,type類型本身是一種什么類型呢?答案也是type。這也就是說type就是它自身的元類。雖然聽起來有點詭異,但這在Python解釋器層面而言是可行的。

type自身就是一個類,并且我們可以從它繼承出新類。這些生成的類也能作為元類,并且使用它們的類可以得到跟使用type一樣的類型。來看以下的例子:
 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
>>> class meta(type):
...   def __new__(cls, class_name, parents, attributes):
...       print "meta.__new__"
...       return super(meta, cls).__new__(cls, class_name, parents, attributes)
...   def __call__(self, *args, **kwargs):
...       print "meta.__call__"
...       return super(meta, self).__call__(*args, **kwargs)
...
>>> class C(object):
...   __metaclass__ = meta
...
meta.__new__
>>> c = C()
meta.__call__
>>> type(C)
<class '__main__.meta'>

請注意當類創(chuàng)建對象時,元類的__call__函數(shù)就被調(diào)用,進而調(diào)用type.__call__創(chuàng)建對象。在下一節(jié),我們將把上面的內(nèi)容融合在一起。
要點集合

假定一個類C自己的元類為my_metaclass并被裝飾器my_class_decorator裝飾。并且,假定my_metaclass本身就是一個類,從type生成。讓我們將上面提到的內(nèi)容融合到一起做一個總結(jié)來顯示C類以及它的對象都是怎么被創(chuàng)建的。首先,讓我們來看看代碼:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class my_metaclass(type):
  def __new__(cls, class_name, parents, attributes):
    print "- my_metaclass.__new__ - Creating class instance of type", cls
    return super(my_metaclass, cls).__new__(cls,
                        class_name,
                        parents,
                        attributes)
 
  def __init__(self, class_name, parents, attributes):
    print "- my_metaclass.__init__ - Initializing the class instance", self
    super(my_metaclass, self).__init__(self)
 
  def __call__(self, *args, **kwargs):
    print "- my_metaclass.__call__ - Creating object of type ", self
    return super(my_metaclass, self).__call__(*args, **kwargs)
 
def my_class_decorator(cls):
  print "- my_class_decorator - Chance to modify the class", cls
  return cls
 
@my_class_decorator
class C(object):
  __metaclass__ = my_metaclass
 
  def __new__(cls):
    print "- C.__new__ - Creating object."
    return super(C, cls).__new__(cls)
 
  def __init__(self):
    print "- C.__init__ - Initializing object."
 
c = C()
print "Object c =", c

現(xiàn)在,你可以花幾分鐘時間測試一下你的理解,并且猜一猜打印輸出的順序。

首先,讓我們來看看Python的解釋器是如何閱讀這部分代碼的,然后我們會對應輸出來加深我們的理解。

1. Python首先看類聲明,準備三個傳遞給元類的參數(shù)。這三個參數(shù)分別為類名(class_name),父類(parent)以及屬性列表(attributs)。

2. Python會檢查__metaclass__屬性,如果設(shè)置了此屬性,它將調(diào)用metaclass,傳遞三個參數(shù),并且返回一個類。

3. 在這個例子中,metaclass自身就是一個類,所以調(diào)用它的過程類似創(chuàng)建一個新類。這就意味著my_metaclass.__new__將首先被調(diào)用,輸入四個參數(shù),這將新建一個metaclass類的實例。然后這個實例的my_metaclass.__init__將被調(diào)用調(diào)用結(jié)果是作為一個新的類對象返回。所以此時C將被設(shè)置成這個類對象。

4. 接下來Python將查看所有裝飾了此類的裝飾器。在這個例子中,只有一個裝飾器。Python將調(diào)用這個裝飾器,將從元類哪里得到的類傳遞給它作為參數(shù)。然后這個類將被裝飾器返回的對象所替代。

5. 裝飾器返回的類類型與元類設(shè)置的相同。

6. 當類被調(diào)用創(chuàng)建一個新的對象實例時,因為類的類型是metaclass,因此Python將會調(diào)用元類的__call__方法。在這個例子中,my_metaclass.__call__只是簡單的調(diào)用了type.__call__,目的是創(chuàng)建一個傳遞給它的類的對象實例。

7. 下一步type.__call__通過C.__new__創(chuàng)建一個對象。

8. 最后type.__call__通過C.__new__返回的結(jié)果運行C.__init__。

9. 返回的對象已經(jīng)準備完畢。

所以基于以上的分析,我們可以看到調(diào)用的順序如下:my_metaclass.__new__首先被調(diào)用,然后是my_metaclass.__init__,然后是my_class_decorator。至此C類已經(jīng)準備完畢(返回結(jié)果就是C)。當我們調(diào)用C來創(chuàng)建一個對象的時候,首先會調(diào)用my_metaclass.__call__(任何對象被創(chuàng)建的時候,Python都首先會去調(diào)用其類的__call__方法),然后C.__new__將會被type.__call__調(diào)用(my_metaclass.__call__簡單調(diào)用了type.__call__),最后是C.__init__被調(diào)用。現(xiàn)在讓我們來看看輸出:
 

?
1
2
3
4
5
6
7
- my_metaclass.__new__ - Creating class instance of type <class '__main__.my_metaclass'>
- my_metaclass.__init__ - Initializing the class instance <class '__main__.C'>
- my_class_decorator - Chance to modify the class <class '__main__.C'>
- my_metaclass.__call__ - Creating object of type <class '__main__.C'>
- C.__new__ - Creating object.
- C.__init__ - Initializing object.
Object c = <__main__.C object at 0x1043feb90> <class '__main__.C'>

關(guān)于元類多說幾句

元類,一門強大而晦澀的技法。在GitHub上搜索__metaclass__得到的結(jié)果多半是指向”cookbook”或其他Python教學材料的鏈接。一些測試用例(諸如Jython中的一些測試用例),或是其他一些寫有__metaclass__ = type的地方只是為了確保新類被正常使用了。坦白地說,這些用例都沒有真正地使用元類。過濾了下結(jié)果,我只能找到兩個地方真正使用了元類:ABCMeta和djangoplugins。

ABCMeta是一個允許注冊抽象基類的元類。如果想了解多些請查看其官方文檔,本文將不會討論它。

對于djangoplugins而言,基本的思想是基于這篇文章article on a simple plugin framework for Python,使用元類是為了創(chuàng)建一個插件掛載系統(tǒng)。我并沒有對其有深入的研究,不過我感覺這個功能可以使用裝飾器來實現(xiàn)。如果你有相關(guān)的想法請在 本文后留言。
總結(jié)筆記

通過理解元類能夠幫助我們更深入的理解Python中類和對象的行為,現(xiàn)實中使用它們的情況可能比文中的例子要復雜得多。大部分元類完成的功能都可以使用裝飾器來實現(xiàn)。所以當你的第一直覺是使用元類來解決你的問題,那么請你停下來先想想這是否必要。如果不是非要使用元類,那么請三思而行。這會使你的代碼更易懂,更易調(diào)試和維護。

延伸 · 閱讀

精彩推薦
Weibo Article 1 Weibo Article 2 Weibo Article 3 Weibo Article 4 Weibo Article 5 Weibo Article 6 Weibo Article 7 Weibo Article 8 Weibo Article 9 Weibo Article 10 Weibo Article 11 Weibo Article 12 Weibo Article 13 Weibo Article 14 Weibo Article 15 Weibo Article 16 Weibo Article 17 Weibo Article 18 Weibo Article 19 Weibo Article 20 Weibo Article 21 Weibo Article 22 Weibo Article 23 Weibo Article 24 Weibo Article 25 Weibo Article 26 Weibo Article 27 Weibo Article 28 Weibo Article 29 Weibo Article 30 Weibo Article 31 Weibo Article 32 Weibo Article 33 Weibo Article 34 Weibo Article 35 Weibo Article 36 Weibo Article 37 Weibo Article 38 Weibo Article 39 Weibo Article 40
主站蜘蛛池模板: 168黄网 | 亚洲视频中文字幕 | 最近2019中文字幕大全视频10 | 久久久久久久久99精品 | 亚洲欧洲在线观看 | 久久精品伊人 | 中文字幕日韩欧美一区二区三区 | 欧美精品一区二区三区在线播放 | 久久99精品久久久久 | 亚洲午夜精品久久久久久高潮 | 久久夜视频 | 成人av片在线观看 | 精品一区二区三区中文字幕老牛 | 免费av电影观看 | 国产中文视频 | 亚洲五月婷婷 | 99热少妇| 97色在线视频 | 国产亚洲视频在线观看 | 噜噜噜噜狠狠狠7777视频 | 中文字幕视频在线免费 | 精品中文字幕一区二区 | 天堂成人av | 日韩欧美一区二区在线视频 | 亚洲激情一区 | 欧日韩在线视频 | 色在线免费观看 | 国产免费成人 | 欧美久久视频 | 黄在线观看 | 一区欧美| 久久久久久综合 | 日韩欧美精品在线 | 久久精品亚洲成在人线av网址 | 久久久久高清 | 日本a在线天堂 | 国产在线综合视频 | 中文字幕在线精品 | 久久久免费电影 | 久久国产视屏 | 91精品国产91久久久久久吃药 |