国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > Python > 正文

Python中的元類編程入門指引

2019-11-25 17:44:01
字體:
來源:轉載
供稿:網(wǎng)友

回顧面向對象編程

讓我們先用 30 秒鐘來回顧一下 OOP 到底是什么。在面向對象編程語言中,可以定義 類,它們的用途是將相關的數(shù)據(jù)和行為捆綁在一起。這些類可以繼承其 父類的部分或全部性質,但也可以定義自己的屬性(數(shù)據(jù))或方法(行為)。在定義類的過程結束時,類通常充當用來創(chuàng)建 實例(有時也簡單地稱為 對象)的模板。同一個類的不同實例通常有不同的數(shù)據(jù),但“外表”都是一樣 ― 例如, Employee 對象 bob 和 jane 都有 .salary 和 .room_number ,但兩者的房間和薪水都各不相同。

一些 OOP 語言(包括 Python)允許對象是 自省的(也稱為 反射)。即,自省對象能夠描述自己:實例屬于哪個類?類有哪些祖先?對象可以用哪些方法和屬性?自省讓處理對象的函數(shù)或方法根據(jù)傳遞給函數(shù)或方法的對象類型來做決定。即使沒有自省,函數(shù)也常常根據(jù)實例數(shù)據(jù)進行劃分,例如,到 jane.room_number 的路線不同于到 bob.room_number 的路線,因為它倆在不同的房間。利用自省, 還可以在安全地計算 jane 所獲獎金的同時,跳過對 bob 的計算,例如,因為 jane 有 .profit_share 屬性,或者因為 bob 是子類 Hourly(Employee) 的實例。

元類編程(metaprogramming)的回答

以上概述的基本 OOP 系統(tǒng)功能相當強大。但在上述描述中有一個要素沒有受到重視:在 Python(以及其它語言)中,類本身就是可以被傳遞和自省的對象。正如前面所講到的,既然可以用類作為模板來生成對象,那么用什么 作為模板來生成類呢?答案當然是 元類(metaclass)。

Python 一直都有元類。但元類中所涉及的方法在 Python 2.2 中才得以更好地公開在人們面前。Python V2.2 明確地不再只使用一個特殊的(通常是隱藏的)元類來創(chuàng)建每個類對象。現(xiàn)在程序員可以創(chuàng)建原始元類 type 的子類,甚至可以用各種元類動態(tài)地生成類。當然,僅僅因為 可以在 Python 2.2 中操作元類,這并不能說明您可能想這樣做的原因。

而且,不需要使用定制元類來操作類的生成。一種不太費腦筋的概念是 類工廠:一種普通的函數(shù),它可以 返回在函數(shù)體內動態(tài)創(chuàng)建的類。用傳統(tǒng)的 Python 語法,您可以編寫:
清單 1. 老式的 Python 1.5.2 類工廠

Python 1.5.2 (#0, Jun 27 1999, 11:23:01) [...]Copyright 1991-1995 Stichting Mathematisch Centrum, Amsterdam>>> def class_with_method(func):...   class klass: pass...   setattr(klass, func.__name__, func)...   return klass...>>> def say_foo(self): print 'foo'...>>> Foo = class_with_method(say_foo)>>> foo = Foo()>>> foo.say_foo()foo

工廠函數(shù) class_with_method() 動態(tài)地創(chuàng)建一個類,并返回該類,這個類包含傳遞給該工廠 的方法/函數(shù)。在返回該類之前,在函數(shù)體內操作類自身。 new 模塊提供了更簡潔的編碼方式,但其中的選項與 類工廠體內定制代碼的選項不同,例如:
清單 2. new 模塊中的類工廠

>>> from new import classobj>>> Foo2 = classobj('Foo2',(Foo,),{'bar':lambda self:'bar'})>>> Foo2().bar()'bar'>>> Foo2().say_foo()foo

在所有這些情形中,沒有將類( Foo 和 Foo2 )的行為直接編寫為代碼, 而是用動態(tài)參數(shù)在運行時調用函數(shù)來創(chuàng)建類的行為。這里要強調的一點是,不僅 實例可以動態(tài)地創(chuàng)建,而且 類本身也可以動態(tài)地創(chuàng)建。

元類:尋求問題的解決方案?

    元類的魔力是如此之大,以至于 99% 的用戶曾有過的顧慮都是不必要的。如果您想知道是否需要它們,則可以不用它們(那些實際需要元類的人們確實清楚自己需要它們,不需要解釋原因)。― Python 專家 Tim Peters

(類的)方法象普通函數(shù)一樣可以返回對象。所以從這個意義上講,類工廠可以是類,就象它們可以是函數(shù)一樣容易,這是顯然的。尤其 是 Python 2.2+ 提供了一個稱為 type 的特殊類,它正是這樣的類工廠。當然,讀者會認識到 type() 不象 Python 老版本的內置函數(shù)那樣“野心勃勃”― 幸運的是,老版本的 type() 函數(shù)的行為是由 type 類維護的(換句話說, type(obj) 返回對象 obj 的類型/類)。作為類工廠的新 type 類,其工作方式與函數(shù) new.classobj 一直所具有的方式相同:
清單 3. 作為類工廠元類的 type

>>> X = type('X',(),{'foo':lambda self:'foo'})>>> X, X().foo()(<class '__main__.X'>, 'foo')

但是因為 type 現(xiàn)在是(元)類,所以可以自由用它來創(chuàng)建子類:
清單 4. 作為類工廠的 type 后代

>>> class ChattyType(type):...   def __new__(cls, name, bases, dct):...     print "Allocating memory for class", name...     return type.__new__(cls, name, bases, dct)...   def __init__(cls, name, bases, dct):...     print "Init'ing (configuring) class", name...     super(ChattyType, cls).__init__(name, bases, dct)...>>> X = ChattyType('X',(),{'foo':lambda self:'foo'})Allocating memory for class XInit'ing (configuring) class X>>> X, X().foo()(<class '__main__.X'>, 'foo')

富有“魔力”的 .__new__() 和 .__init__() 方法很特殊,但在概念上,對于任何其它類,它們的工作方式都是一樣的。 .__init__() 方法使您能配置所創(chuàng)建的對象; .__new__() 方法使您能定制它的分配。當然,后者沒有被廣泛地使用,但對于每個 Python 2.2 new 樣式的類(通常通過繼承而不是覆蓋),都存在該方法。

需要注意 type 后代的一個特性;它常使第一次使用元類的人們“上圈套”。按照慣例,這些方法的第一個參數(shù)名為 cls ,而不是 self ,因為這些方法是在 已生成的類上進行操作的,而不是在元類上。事實上,關于這點沒什么特別的;所有方法附加在它們的實例上,而且元類的實例是類。非特殊的 名稱使這更明顯:
清單 5. 將類方法附加在所生成的類上

>>> class Printable(type):...   def whoami(cls): print "I am a", cls.__name__...>>> Foo = Printable('Foo',(),{})>>> Foo.whoami()I am a Foo>>> Printable.whoami()Traceback (most recent call last):TypeError: unbound method whoami() [...]

所有這些令人驚訝但又常見的做法以及便于掌握的語法使得元類的使用更容易,但也讓新用戶感到迷惑。對于其它語法有幾個元素。但這些新變體的解析順序需要點技巧。類可以從其祖先那繼承元類 ― 請注意,這與將元類 作為祖先 不一樣(這是另一處常讓人迷惑的地方)。對于老式類,定義一個全局 _metaclass_ 變量可以強制使用定制元類。但大多數(shù)時間,最安全的方法是,在希望通過定制元類來創(chuàng)建類時,設置該類的 _metaclass_ 類屬性。必須在類定義本身中設置變量,因為 如果稍后(在已經(jīng)創(chuàng)建類對象之后)設置屬性 ,則不會使用元類。例如:
清單 6. 用類屬性設置元類

>>> class Bar:...   __metaclass__ = Printable...   def foomethod(self): print 'foo'...>>> Bar.whoami()I am a Bar>>> Bar().foomethod()foo

用這種“魔力”來解決問題

至此,我們已經(jīng)了解了一些有關元類的基本知識。但要使用元類,則比較復雜。使用元類的困難之處在于,通常在 OOP 設計中,類其實 做得不多。對于封裝和打包數(shù)據(jù)和方法,類的繼承結構很有用,但在具體 情形中,人們通常使用實例。

我們認為元類在兩大類編程任務中確實有用。

第一類(可能是更常見的一類)是在設計時不能 確切地知道類需要做什么。顯然,您對它有所了解,但某個特殊的細節(jié) 可能取決于稍后才能得到的信息。“稍后”本身有兩類:(a)當應用程序使用庫模塊時;(b)在運行時,當某種情形存在時。這類很接近于通常所說的“面向方面的編程(Aspect-Oriented Programming,AOP)”。我們將展示一個我們認為非常別致的示例:
清單 7. 運行時的元類配置

% cat dump.py#!/usr/bin/pythonimport sysif len(sys.argv) > 2:  module, metaklass = sys.argv[1:3]  m = __import__(module, globals(), locals(), [metaklass])  __metaclass__ = getattr(m, metaklass)class Data:  def __init__(self):    self.num = 38    self.lst = ['a','b','c']    self.str = 'spam'  dumps  = lambda self: `self`  __str__ = lambda self: self.dumps()data = Data()print data% dump.py<__main__.Data instance at 1686a0>

正如您所期望的,該應用程序打印出 data 對象相當常規(guī)的描述(常規(guī)的實例對象)。但如果將 運行時參數(shù)傳遞給應用程序,則可以得到相當不同的結果:
清單 8. 添加外部序列化元類

% dump.py gnosis.magic MetaXMLPickler<?xml version="1.0"?><!DOCTYPE PyObject SYSTEM "PyObjects.dtd"><PyObject module="__main__" class="Data" id="720748"><attr name="lst" type="list" id="980012" > <item type="string" value="a" /> <item type="string" value="b" /> <item type="string" value="c" /></attr><attr name="num" type="numeric" value="38" /><attr name="str" type="string" value="spam" /></PyObject>

這個特殊的示例使用 gnosis.xml.pickle 的序列化樣式,但最新的 gnosis.magic 包還包含元類序列化器 MetaYamlDump 、 MetaPyPickler 和 MetaPrettyPrint 。而且, dump.py “應用程序”的用戶可以從任何定義了任何期望的 MetaPickler 的 Python 包中利用該“MetaPickler”。出于此目的而 編寫合適的元類如下所示:
清單 9. 用元類添加屬性

class MetaPickler(type):  "Metaclass for gnosis.xml.pickle serialization"  def __init__(cls, name, bases, dict):    from gnosis.xml.pickle import dumps    super(MetaPickler, cls).__init__(name, bases, dict)    setattr(cls, 'dumps', dumps)

這種安排的過人之處在于應用程序程序員不需要了解要使用哪種序列化 ― 甚至不需要了解是否 在命令行添加序列化或其它一些跨各部分的能力。

也許元類最常見的用法與 MetaPickler 類似:添加、刪除、重命名或替換所產(chǎn)生類中定義的方法。在我們的示例中,在創(chuàng)建類 Data (以及由此再創(chuàng)建隨后的每個實例)時,“本機” Data.dump() 方法被應用程序之外的某個方法所替代。


使用這種“魔力”來解決問題的其它方法

存在著這樣的編程環(huán)境:類往往比實例更重要。例如, 說明性迷你語言(declarative mini-languages)是 Python 庫,在類聲明中直接表示了它的程序邏輯。David 在其文章“ Create declarative mini-languages”中研究了此問題。在這種情形下,使用元類來影響類創(chuàng)建過程是相當有用的。

一種基于類的聲明性框架是 gnosis.xml.validity 。 在此框架下,可以聲明許多“有效性類”,這些類表示了一組有關有效 XML 文檔的約束。這些聲明非常接近于 DTD 中所包含的那些聲明。例如,可以用以下代碼來配置一篇“dissertation”文檔:
清單 10. simple_diss.py gnosis.xml.validity 規(guī)則

from gnosis.xml.validity import *class figure(EMPTY):   passclass _mixedpara(Or):   _disjoins = (PCDATA, figure)class paragraph(Some):  _type = _mixedparaclass title(PCDATA):   passclass _paras(Some):    _type = paragraphclass chapter(Seq):    _order = (title, _paras)class dissertation(Some): _type = chapter

如果在沒有正確組件子元素的情形下嘗試實例化 dissertation 類,則會產(chǎn)生一個描述性異常;對于每個 子元素,亦是如此。當只有一種明確的方式可以將參數(shù)“提升”為正確的類型 時,會從較簡單的參數(shù)來生成正確的子元素。

即使有效性類常常(非正式)基于預先存在的 DTD,這些類的實例也還是將自己打印成簡單的 XML 文檔片段,例如:
清單 11. 基本的有效性類文檔的創(chuàng)建

>>> from simple_diss import *>>> ch = LiftSeq(chapter, ('It Starts','When it began'))>>> print ch<chapter><title>It Starts</title><paragraph>When it began</paragraph></chapter>

通過使用元類來創(chuàng)建有效性類,我們可以從類聲明中生成 DTD(我們在這樣做的同時,可以向這些有效性類額外添加一個方法):
清單 12. 在模塊導入期間利用元類

>>> from gnosis.magic import DTDGenerator, /...             import_with_metaclass, /...             from_import>>> d = import_with_metaclass('simple_diss',DTDGenerator)>>> from_import(d,'**')>>> ch = LiftSeq(chapter, ('It Starts','When it began'))>>> print ch.with_internal_subset()<?xml version='1.0'?><!DOCTYPE chapter [<!ELEMENT figure EMPTY><!ELEMENT dissertation (chapter)+><!ELEMENT chapter (title,paragraph+)><!ELEMENT title (#PCDATA)><!ELEMENT paragraph ((#PCDATA|figure))+>]><chapter><title>It Starts</title><paragraph>When it began</paragraph></chapter>

包 gnosis.xml.validity 不知道 DTD 和內部子集。那些概念和能力完全由元類 DTDGenerator 引入進來,對 gnosis.xml.validity 或 simple_diss.py 不做 任何更改。 DTDGenerator 不將自身的 .__str__() 方法替換進它產(chǎn)生的類 ― 您仍然可以打印簡單的 XML 片段 ― 但元類可以方便地修改這種富有“魔力”的方法。


元帶來的便利

為了使用元類以及一些可以在面向方面的編程中所使用的樣本元類,包 gnosis.magic 包含幾個實用程序。其中最 重要的實用程序是 import_with_metaclass() 。 在上例中所用到的這個函數(shù)使您能導入第三方的模塊,但您要用定制元類而不是用 type 來創(chuàng)建所有模塊類。無論您想對第三方模塊賦予什么樣的新能力,您都可以在創(chuàng)建的元類內定義該能力(或者從其它地方一起獲得)。 gnosis.magic 包含一些可插入的序列化元類;其它一些包可能包含跟蹤能力、對象持久性、異常日志記錄或其它能力。

import_with_metclass() 函數(shù)展示了元類編程的幾個性質:
清單 13. [gnosis.magic] 的 import_with_metaclass()

def import_with_metaclass(modname, metaklass):  "Module importer substituting custom metaclass"  class Meta(object): __metaclass__ = metaklass  dct = {'__module__':modname}  mod = __import__(modname)  for key, val in mod.__dict__.items():    if inspect.isclass(val):      setattr(mod, key, type(key,(val,Meta),dct))  return mod

在這個函數(shù)中值得注意的樣式是,用指定的元類生成普通的類 Meta 。但是,一旦將 Meta 作為祖先添加之后,也用定制元類來生成它的后代。原則上,象 Meta 這樣的類 既可以帶有元類生成器(metaclass producer) 也可以帶有一組可繼承的方法 ― Meta 類的這兩個方面是無關的。

發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表
主站蜘蛛池模板: 福州市| 肥乡县| 卫辉市| 奉新县| 武穴市| 田东县| 临高县| 玉环县| 石渠县| 普宁市| 双牌县| 阿图什市| 修文县| 兴隆县| 新建县| 林西县| 泗水县| 汤原县| 东丽区| 习水县| 平山县| 理塘县| 平定县| 伊宁市| 双流县| 榆树市| 特克斯县| 乌审旗| 上蔡县| 安康市| 磴口县| 镇原县| 大厂| 石家庄市| 内乡县| 荆门市| 岳西县| 阿巴嘎旗| 汶上县| 德兴市| 台北县|