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%的時間裡最好使用上面兩種技術來實現對類的修改。