1. 程式人生 > >Python高階用法總結--元類

Python高階用法總結--元類

type()

動態語言和靜態語言最大的不同,就是函式和類的定義,不是編譯時定義,而是執行時動態建立的。

比方說我們要定義一個 Hello 的 class ,就寫一個hello.py 模組:

class Hello(object):
        def hello(self, name='world'):
                print('Hello, %s' %name)

當 Python 直譯器載入 hello 模組時,就會依次執行該模組的所有語句,執行結果就是動態創建出一個 Hello 的 class 物件,測試如下:

from hello import Hello

h 
= Hello() h.hello() # Hello,world print(type(Hello)) # <type 'type'> print(type(h)) #<class 'hello.Hello'>

type() 函式可以檢視一個型別或變數的型別,Hello 是一個class,它的型別就是 type ,而 h 是一個例項,它的型別就是 class Hello。

我們說 class 的定義是執行時動態建立的,而建立 class 的方法就是使用 type() 函式。

type() 函式既可以返回一個物件的型別,又可以創建出新的型別,比如,我們可以通過 type() 函式創建出 Hello 類,而無需通過 class Hello(object) 來定義。

def fn(self, name='world'):  # 先定義函式
        print('Hello, %s'%name)

Hello = type('Hello', (object,), dict(hello=fn))  # 建立Hello class

h = Hello()

h.hello()   # Hello, world

print(type(Hello))  # <type 'type'>

print(type(h))  # <class '__main__.Hello'>

要建立一個 class 物件, type() 函式依次傳入3個引數:

  1. class 的名稱。
  2. 繼承的父類集合,注意 Python 支援多重繼承,如果只有一個父類,別忘了 tuple 的單元素寫法。
  3. class 的方法名稱與函式繫結,這裡我們把函式 fn 繫結到方法名上。

通過 type() 函式建立的類和直接寫 class 是完全一樣的,因為Python 直譯器遇到 class 定義時,僅僅是掃描一下 class 定義的語法,然後呼叫 type() 函式創建出 class。

正常情況下,我們都用 class xxx 來定義類,但是, type() 函式也允許我們動態創建出類來,也就是說,動態語言本身支援執行期動態建立類,這和靜態語言有非常大的不同,要在靜態語言執行期建立類,必須構造原始碼字串再呼叫編譯器,或者藉助一些工具生成位元組碼實現,本質上都是動態編譯,非常複雜。

metaclass

除了使用 type() 動態建立類以外,要控制類的建立行為,還可以使用 metaclass。

metaclass 直譯為元類,簡單的解釋就是:

當我們定義了類以後,就可以根據這個類創建出例項,所以:先定義類,然後建立例項。

但是如果我們想創建出類呢?那就必須根據 metaclass 創建出類,所以:先定義 metaclass ,然後建立類。

連線起來就是:先定義metaclass ,就可以建立類,最後建立例項。

所以,metaclass 允許你建立類或者修改類。換句話說,你可以把類看出是 metatclass 創建出來的例項。

metaclass 是 Python 面向物件裡最難理解,也是最難使用的魔術程式碼。支援正常情況下,我們不會碰到。

我們先看一個簡單的例子,這個metaclass 的類名總是 Metaclass 結尾,以便清楚地表示這是一個 metaclass:

# metaclass 是建立類,所以必須從 type 型別派生
class ListMetaclass(type):
        def __new__(cls, name, bases, attrs):
                attrs['add'] = lambda self, value: self.paaned(value)
                return type.__new__(cls, name, bases, attrs)

class MyList(list):
        __etaclass__ = ListMetaclass  # 指示使用ListMetaclass 來定製類

當我們寫下 __metaclass__ = ListMetaclass 語句時,魔術就生效了,它指示 Python 直譯器在建立 MyList 時,要通過ListMetaclass.__new__() 來建立,在此,我們可以修改類的定義,比如加上新的方法,然後,返回修改後的定義。

__new__() 方法接收到的引數依次是:

  1. 當前準備建立的類的物件。
  2. 類的名字。
  3. 類繼承的父類集合。
  4. 類的方法集合

測試一下 MyList 是否可以呼叫 add() 方法:

L= MyList()
L.add(1)
print L # [1]

而普通的 list 沒有 add() 方法

l = list()
l.add()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'list' object has no attribute 'add'

動態修改有什麼意義? 直接在MyList 定義中寫上 add() 方法不是更加簡單嗎? 正常情況先,確實應該直接寫,通過metaclass 修改純屬變態。

但是,總會遇到需要通過 metaclass 修改類的定義的。

ORM 就是一個典型的例子

ORM 全稱 'Object Relational Mapping ' 即物件-關係對映,就是把關係資料庫的一行對映為一個物件,也就是一個類對應一個表,這樣,寫程式碼更簡單,不用直接操作 SQL 語句。

要編寫一個 ORM 框架,所有的類都只能動態定義,因為只有使用者才能根據表的結構定義出對應的類來。

例項:編寫 ORM 框架

編寫底層模組的第一步,就是先把呼叫介面寫出了。比如,使用者如果使用這個ORM框架,想定義一個 User 類來操作對應的資料庫表 User。

class User(Model):
        # 定義類的屬性到列的對映
        id = IntegerField('id')
        name = StringField('username')
        email = StringField('email')
        password = StringField('password')

# 建立一個例項
u = User(id=123, name='Michael', email='[email protected]', password='pwd')
# 儲存到資料庫
u.save()

其中,父類 Model 和屬性型別 StringField 、IntegerField 是由ORM框架提供的,剩下的魔術方法比如 save() 全部由 metaclass 自動完成。雖然 metaclass 的編寫會比較複雜,但ORM 的使用者用起來卻異常簡單。

現在,我們就按照上面的介面來實現 ORM

首先來定義 Field 類,它負責儲存資料庫表的欄位名和欄位型別:

class Field(object):
        def __init__(self, name, column_type):
                self.name = name
                self.column_type = column_type

        def __str__(self):
                return '<%s:%s>' % (self.__class__.__name__, self.name)

在 Field 的基礎上,進一步定義各種型別的 Field ,比如 StringFielf、IntegerField 等等:

class StringField(Field):
        def __init__(self, name):
                super(StringField, self).__init__(name, 'varchar(100)')

class IntegerField(Field):
        def __init__(self, name):
            super(IntaegerField, self).__init__(name, 'bigint')

下一步,就是編寫最複雜的 ModelMateclass 了:

class ModelMetaclass(type):
        def __new__(cls, name, bases, attrs):
                if name=='Model':
                        return type.__new__(cls, name, bases, attrs)
                mappings = dict()
                for k, v in attrs.iteritems():
                        if isinstance(v, Field):
                                print('Fund mapping: %s==>%s' % (k, v))
                                mappings[k]  = v
                for k in mappings.iterkeys():
                        attrs.pop(k)

                attrs['__table__'] = name # 假設表名和類名一致
                attrs['__mappings__'] = mappings # 儲存屬性和列的對映關係
                return type.__new__(cls, name, bases, attrs)

以及基類 Model:

class Model(dict):
        __metaclass__ = ModelMetaclass

        def __init__(slef, **kw):
                super(Model, self).__init__(**kw)

        def __getattr(self, key):
                try:
                        return self[key]
                except KeyError:
                        raise AttributeError(r" 'model' object has no 
 attribute '%s' " % key)

        def __setattr__(self, key, value):
                self[key] = value

        def save(self):
                fields = []
                params = []
                args = []
                for k, v in self.__mappings__.iteritems():
                        fields.append(v.name)
                        params.append('?')
                        args.append(getattr(self, k, None))
                sql = 'insert into %s (%s) values (%s)' % (self.__table__, ','.join(fields), ','.join(params))
                print('SQL: %s' % sql)
                print('ARGS: %s' % str(args))

當用戶定義一個 class User(Model) 時, Python 直譯器首先在當前類 User 的定義中查詢 __metaclass__ ,如果沒有找到,就繼續在父類 Model 中查詢 __metaclass__ 。找到了,就使用 Model 中定義的 __metaclass__ 的ModelMetaclass 來建立 User 類,也就是說, metaclass 可以隱式地繼承到子類,但子類自己卻感覺不到。

在 ModelMetaclass 中,一共做了幾件事情:

  1. 排除掉對 Model 類的修改。
  2. 在當前類(比如User)中查詢定義的類所有屬性,如果找到一個 Field 屬性,就把它儲存到一個 __mappings__ 的 dict 中,同時從類屬性中刪除該 Field 屬性,否則,容易造成執行時錯誤
  3. 把表名儲存到 __table__ 中,這裡簡化為表名預設為類名。

在 Model 類中,就可以定義各種操作資料庫的方法,比如 save(), delete(), find(), update() 等等。

編寫測試程式碼:

u = User(id=12345, name='Michael', email='[email protected]', password='my-pwd')
u.save()

# 輸出如下
Found model: User
Found mapping: email ==> <StringField:email>
Found mapping: password ==> <StringField:password>
Found mapping: id ==> <IntegerField:uid>
Found mapping: name ==> <StringField:username>
SQL: insert into User (password,email,username,uid) values (?,?,?,?)
ARGS: ['my-pwd', '[email protected]', 'Michael', 12345]

最後解釋一下類屬性和例項屬性。直接在class中定義的是類屬性:

class Student(object):
    name = 'Student'

例項屬性必須通過例項來繫結,比如self.name = 'xxx'。來測試一下:

>>> # 建立例項s:
>>> s = Student()
>>> # 列印name屬性,因為例項並沒有name屬性,所以會繼續查詢class的name屬性:
>>> print(s.name)
Student
>>> # 這和呼叫Student.name是一樣的:
>>> print(Student.name)
Student
>>> # 給例項繫結name屬性:
>>> s.name = 'Michael'
>>> # 由於例項屬性優先順序比類屬性高,因此,它會遮蔽掉類的name屬性:
>>> print(s.name)
Michael
>>> # 但是類屬性並未消失,用Student.name仍然可以訪問:
>>> print(Student.name)
Student
>>> # 如果刪除例項的name屬性:
>>> del s.name
>>> # 再次呼叫s.name,由於例項的name屬性沒有找到,類的name屬性就顯示出來了:
>>> print(s.name)
Student


因此,在編寫程式的時候,千萬不要把例項屬
性和類屬性使用相同的名字。

在我們編寫的ORM中,ModelMetaclass會刪除掉User類的所有類屬性,目的就是避免造成混淆。

摘抄至https://www.liaoxuefeng.com/wiki/001374738125095c955c1e6d8bb493182103fac9270762a000/001386820064557c69858840b4c48d2b8411bc2ea9099ba000