python diary (part 3)-metaprogramming

http://www.ibm.com/developerworks/cn/linux/l-pymeta/index.html?S_TACT=105AGX52&S_CMP=techcsdn
http://www-128.ibm.com/developerworks/cn/linux/l-pymeta2/?S_TACT=105AGX52&S_CMP=techcsdn
http://www.ibm.com/developerworks/cn/linux/l-pymeta3.html?ca=drs-tp4707&S_TACT=105AGX52&S_CMP=techcsdn

"一些 OOP 语言(包括 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
 
这段代码首先给我一个例子,证明pass关键词确有其用.
其次在这段不长的代码里在我这个被oo洗脑的人看来,每一行都有magic.
class klass: pass #就像是做了一个类实例(这样说很怪)
setattr(klass, func.__name__, func) #类的prototype是可以被修改的,
#如果用js写起来大概是klass.prototype.<func.__name__> = func
#只是js里没有func.__name__这种内置的属性。

Foo = class_with_method(say_foo) #类实例作为返回值传递
foo = Foo() #用类实例来实例化一个对象

不得不说,这比js里的all is function的基本思想更接近于oo,更接近于人的思维,却也保留了语法上的动态。

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

这段代码在ironpython上跑不过,提示没有new module,当然也就没有classobj(即我所谓的类实例)
全凭猜想,我以为classobj接受三个参数classob(nameString,superclassTuple,bodyDict)
而superclassTuple为什么要做成(Foo,)这个样子,也让人十分疑惑。

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

虽然没有classobj,不过ironpython是内置支持type的,有趣的是
>>>dict
<type 'dict'>
>>>type
<type '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 X
Init'ing (configuring) class X
>>> X, X().foo()
(<class '__main__.X'>, 'foo')

"需要注意 type 后代的一个特性;它常使第一次使用元类的人们“上圈套”。按照惯例,这些方法的第一个参数名为 cls ,而不是 self ,因为这些方法是在 已生成的类上进行操作的,而不是在元类上。事实上,关于这点没什么特别的;所有方法附加在它们的实例上,而且元类的实例是类。非特殊的名称使这更明显"

在没有读懂前一段代码的情况下,我决定再往后看一下。

>>> 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() [...]

我把这段代码写成

>>>class A(type):
... def who(cls):print 'I am',cls.__name__
... 
>>>a=A('Foo',(),{})
>>>a.who()
I am Foo
>>>A.who()
Traceback (most recent call lass):
  File , line 0, in <stdin>##40
TypeError: who() takes exactly 1 argument (0 given)
>>>A.who(A)
Traceback (most recent call lass):
  File , line 0, in <stdin>##41
TypeError: unbound method who() must be called with A instance as first argument (got type instance instead)
>>>A.who(a)
I am Foo
>>>b=A(type('FooB'),(),{})) #这里的type是有点画蛇添足了
>>>b.who()
Traceback (most recent call lass):
  File , line 0, in <stdin>##41
AttributeError: type object 'type' has no attribute 'who'
>>>b
<type 'type'>
>>>b.__name__
'type'
>>>c=type('FooC',(),{})
>>>c.__name__
'FooC'

至此可以看出来如果像这样定义class A(type),实例化的时候是会隐式调用type()的,有点类似于c++的自动构造函数,然而python里又必须是隐式的,显式会出现二义性(从试验结果看,似乎只有type被传入),因为type本身也是一个type。意思是所有,对象是类的实例,类都是type的实例,然而type也是类,type是type的实例(事实上这和我们这些语言的使用者完全无关,而这个逻辑也是无稽的)。

"对于老式类,定义一个全局 _metaclass_ 变量可以强制使用定制元类。但大多数时间,最安全的方法是,在希望通过定制元类来创建类时,设置该类的 _metaclass_ 类属性。必须在类定义本身中设置变量,因为 如果稍后(在已经创建类对象之后)设置属性,则不会使用元类。例如:"


清单 6. 用类属性设置元类
>>> class Bar:
...     __metaclass__ = Printable
...     def foomethod(self): print 'foo'
...
>>> Bar.whoami()
I am a Bar
>>> Bar().foomethod()
foo
 
而通过在运行时指定__metaclass__可以获得额外的动态效果,如:
"
% cat dump.py
#!/usr/bin/python
import sys
if 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>

清单 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)
"
我没有去细读,不过看来python是有对象xml序列化和反序列化的标准和现成的可调用的类。

"
一种基于类的声明性框架是 gnosis.xml.validity 。在此框架下,可以声明许多“有效性类”,这些类表示了一组有关有效 XML 文档的约束。这些声明非常接近于 DTD 中所包含的那些声明。例如,可以用以下代码来配置一篇“dissertation”文档:


清单 10. simple_diss.py gnosis.xml.validity 规则
from gnosis.xml.validity import *
class figure(EMPTY):      pass
class _mixedpara(Or):     _disjoins = (PCDATA, figure)
class paragraph(Some):    _type = _mixedpara
class title(PCDATA):      pass
class _paras(Some):       _type = paragraph
class chapter(Seq):       _order = (title, _paras)
class dissertation(Some): _type = chapter


如果在没有正确组件子元素的情形下尝试实例化 dissertation 类,则会产生一个描述性异常;对于每个子元素,亦是如此。当只有一种明确的方式可以将参数“提升”为正确的类型 时,会从较简单的参数来生成正确的子元素。

即使有效性类常常(非正式)基于预先存在的 DTD,这些类的实例也还是将自己打印成简单的 XML 文档片段,例如:


清单 11. 基本的有效性类文档的创建
>>> 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>
 


通过使用元类来创建有效性类,我们可以从类声明中生成 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__() 方法替换进它产生的类 — 您仍然可以打印简单的 XML 片段 — 但元类可以方便地修改这种富有“魔力”的方法。
"

我也没有细读,大意是使用simple_diss.py gnosis.xml.validity,可以用类似于python的类声明来完成类似于dtd的规则描述,而编写这种python本身又和dtd何其相似。

"
元带来的便利

为了使用元类以及一些可以在面向方面的编程中所使用的样本元类,包 gnosis.magic 包含几个实用程序。其中最重要的实用程序是 import_with_metaclass() 。在上例中所用到的这个函数使您能导入第三方的模块,但您要用定制元类而不是用 type 来创建所有模块类。无论您想对第三方模块赋予什么样的新能力,您都可以在创建的元类内定义该能力(或者从其它地方一起获得)。 gnosis.magic 包含一些可插入的序列化元类;其它一些包可能包含跟踪能力、对象持久性、异常日志记录或其它能力。

import_with_metclass() 函数展示了元类编程的几个性质:


清单 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
 


在这个函数中值得注意的样式是,用指定的元类生成普通的类 Meta 。但是,一旦将 Meta 作为祖先添加之后,也用定制元类来生成它的后代。原则上,象 Meta 这样的类 既可以带有元类生成器(metaclass producer) 也可以带有一组可继承的方法 — Meta 类的这两个方面是无关的。
"

这段没怎么读懂。

 

posted @ 2008-03-06 20:32 nfa2dfa 阅读(85) 评论(0)  编辑 收藏 所属分类: python

标题  
姓名  
主页
Email (博主才能看到) 
验证码 *  看不清,换一张 [登录][注册]
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      


相关链接: