1. 程式人生 > >Python面向物件程式設計,超程式設計metaclass

Python面向物件程式設計,超程式設計metaclass

面向物件的最重要概念就是類class和例項instance,類是抽象的模板,例項是根據類創建出來的具體物件。

沒有繼承類的情況下,就使用object類,這是所有類最終都會繼承的類。

類的__init__方法:
由於類起到模板的作用,所以在建立例項的時候,把一些我們認為必須繫結的屬性寫進特殊定義的__init__方法:

class Student(object):
    def __init__(self, name, score):
        self.name = name
        self.score = score

其他不是必須繫結的只需要使用instance.attribute = value

來動態建立新的物件屬性。

資料封裝:
每個例項擁有各自的資料,我們可以封裝成函式來訪問這些資料:

class Student(object):
    def print_score(self):
        print('%s: %s' % (self.name, self.score))

定義一個方法,除了第一個引數是self以外,其他的和普通函式一樣。

訪問限制:
如果我們要讓內部屬性不被外部訪問,那麼我們可以在屬性的名稱前面加上兩個下劃線__,這樣它就成了一個私有變數,只有內部可以訪問:

class Student(object):
    def __init__(self, name, score):
        self.__name = name
        self.__score = score

這樣就確保了外部不能直接修改物件內部的狀態,但是外部依然需要訪問name和score這兩個屬性,可以給類新增方法:

 def get_name(self):
        return self.__name

如果又需要外部修改score:

def set_score(self, score):
        self.__score = score

有時候我們可以看到以一個下劃線開頭的變數名,這樣的變數外部可以訪問,但是意思是請不要隨意訪問。


繼承

我們定義一個class的時候,可以從某個現有的類繼承,新的類為子類subclass,被繼承的類為基類,父類或者超類:

class Animal(object):
    def run(self):
        print('Animal is running...')
class Dog(Animal):
    pass

繼承的好處是子類獲得了父類的全部功能,如果重寫方法,相當於覆蓋了父類的方法。在繼承關係中,如果一個例項的資料型別是某個子類,那它的資料型別也可以是父類。

靜態語言和動態語言:

class Timer(object):
    def run(self):
        print('Start...')

對於Java那樣的靜態語言來說,如果需要傳入animal型別,則傳入的物件必須是animal型別或者它的子類,否則,無法呼叫run()方法。但是對於Python這樣的動態語言來說,則不一定需要傳入animal型別,只需要傳入的物件有一個run()方法就可以了。
這就是動態語言的鴨子型別,不要求嚴格的繼承體系,一個物件只要看起來像鴨子,走起來像鴨子,就可以被看作是鴨子。

判斷物件的型別:

>>> isinstance(h, Husky)
True


深入理解元類metaclass:

在Python和大多數程式語言中,類就是一組用來描述如何生成一個物件的程式碼段,但是Python中類同樣也是一種物件:只要使用class關鍵字,直譯器執行的時候就會在記憶體中建立一個物件,名字是ObjectCreator,這個物件(類)自身擁有建立物件的能力,這也是它是一個類的原因,但是它的本質仍然是一個物件,既然這是一個物件,那麼可以:

  • 可以將它賦值給一個變數
  • 可以拷貝
  • 可以為它增加屬性!!!
  • 可以作為函式的引數進行傳遞

動態的建立類:

>>> def choose_class(name):
…       if name == 'foo':
…           class Foo(object):
…               pass
…           return Foo     # 返回的是類,不是物件,不是類的例項
…       else:
…           class Bar(object):
…               pass
…           return Bar
…
>>> MyClass = choose_class('foo')
>>> print MyClass              # 這裡同樣得到的也是類,不是物件例項
<class '__main__'.Foo>
>>> print MyClass()            # 加上括號相當於建立了類的例項,所以這裡是物件
<__main__.Foo object at 0x89c6d4c>

類也是物件,所以可以動態建立,那麼就在函式中建立類,像上面那樣。但是這樣仍然需要自己編寫整個類的程式碼。這裡就開始使用到強大的內建函式type()了,這個函式不僅能讓你知道物件的型別是什麼,還能動態的建立類

type可以接受一個類的描述作為引數,然後返回一個類:
type(類名,父類的元組(這是假設有繼承關係的情況下,可以為null),包含屬性的字典(名稱和值))

class MyShinyClass(object):  # 這個類可以通過下面的方式手動建立
    pass
MyShinyClass = type('MyShinyClass', (), {})  # 返回一個類物件,這裡MyShinyClass作為類名,也當做變數作為類的引用,類與變數不同。
print MyShinyClass()   # 建立該類的例項
--------------

class Foo(object):
    bar = True
Foo = type('Foo', (), {'bar':True})  # 接受字典來定義類的屬性
-----------------

class FooChild(Foo):
    pass
FooChild = type('FooChild', (Foo,),{})  # 可以向Foo繼承,它的bar屬性就繼承自父類
-----------------

def echo_bar(self):
    print self.bar
FooChild = type('FooChild', (Foo,), {'echo_bar': echo_bar})  # 為類增加方法,將函式作為屬性賦值就可以了

到底什麼是元類:
元類就是用來建立類這種物件的東西,元類就是類的類:

MyClass = MetaClass()
MyObject = MyClass()
MyClass = type('MyClass', (), {})

函式type實際上是一個元類,就是Python在背後用來建立所有類的元類,可以把元類稱之為“類工廠”,type是Python的內建元類。

可以在寫一個類的時候新增__metaclass__屬性:

class Foo(object):  # 首先寫下,但是這時候類物件還沒有在記憶體中建立
	__metaclass__ = something…   # 這樣會用元類來建立這個Foo類,如果有這個屬性就用它建立,如果沒有就用內建type建立這個類

具體分析:

class Foo(Bar):
    pass

當寫下如上程式碼的時候,Python首先看Foo中有沒有metaclass屬性。如果有,會在記憶體中通過metaclass建立一個名字為Foo的類物件。反之,它會在父類Bar中尋找metaclass屬性。如果在任何父類中都找不到metaclass,它就會在模組層次中去尋找metaclass。如果最後最後都還是找不到這個屬性,那麼就會用內建的type來建立這個類物件。



自定義metaclass:

__metaclass__中可以放置什麼程式碼呢,我們需要放入用來建立類的東西。metaclass的主要用處就是當建立類的時候可以自由的改變類的結構,比如說你想根據當前情形建立符合當前上下文的類。

# 元類會自動將你通常傳給‘type’的引數作為自己的引數傳入,返回一個類物件,將裡面的屬性都轉換為大寫形式
def upper_attr(future_class_name, future_class_parents, future_class_attr):
    #  選擇所有不以'__'開頭的屬性
    attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith('__'))
Python

    # 將它們轉為大寫形式
    uppercase_attr = dict((name.upper(), value) for name, value in attrs)

    # 通過'type'來做類物件的建立
    return type(future_class_name, future_class_parents, uppercase_attr)

__metaclass__ = upper_attr  #  作用到模組中的所有類

----------------------------------------------------------
class Foo(object):
    # 我們也可以只在這裡定義__metaclass__,這樣就只會作用於這個類中
    bar = 'bip'
print hasattr(Foo, 'bar')
# 輸出: False
print hasattr(Foo, 'BAR')
# 輸出:True 
f = Foo()
print f.BAR
# 輸出:'bip'

上面的例子我們使用了一個函式來當做元類,下面我們使用一個真正的class來當做元類:

# 請記住,'type'實際上是一個類,所以,你可以從type繼承
class UpperAttrMetaClass(type):
    # __new__ 是在__init__之前被呼叫的特殊方法
    # __new__是用來建立物件並返回之的方法
    # 而__init__只是用來將傳入的引數初始化給物件
    # 你很少用到__new__,除非你希望能夠控制物件的建立
    # 這裡,建立的物件是類,我們希望能夠自定義它,所以我們這裡改寫__new__
    # 如果你希望的話,你也可以在__init__中做些事情
    def __new__(upperattr_metaclass, future_class_name, future_class_parents, future_class_attr):
        attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith('__'))
        uppercase_attr = dict((name.upper(), value) for name, value in attrs)
        # 這裡需要使用__new__方法,基本的OOP程式設計
        return type.__new__(upperattr_metaclass, future_class_name, future_class_parents, uppercase_attr)

上面注意到引數upperattr_metaclass,類方法的第一個引數總是代表當前的例項,就好比self,在真是的產品程式碼中一個metaclass如下所示:

class UpperAttrMetaclass(type):
    def __new__(cls, name, bases, dct):
        attrs = ((name, value) for name, value in dct.items() if not name.startswith('__')
        uppercase_attr  = dict((name.upper(), value) for name, value in attrs)
        return type.__new__(cls, name, bases, uppercase_attr)
        # 這裡我們使用super方法使之更清晰,可以從元類繼承,可以從type繼承
        return super(UpperAttrMetaclass, cls).__new__(cls, name, bases, uppercase_attr)

總之,元類的主要用途就是建立API,比如說SQLAlchemy的ORM就是基於元類實現的。Python中的一切都是物件,要麼是類的例項,要麼是元類的例項。
當不希望使用元類來對類做修改的時候,可以通過其他兩種技術來動態的修改類:

  • 猴子補丁
  • 類裝飾器
    99%的時間裡最好使用上面兩種技術來實現對類的修改。