Trait属性的功能

Traits库为对象的属性增加了类型定义的功能,此外还提供了如下的额外功能:

  • 初始化:每个Trait属性都有自己的缺省值。
  • 验证:Trait属性都有明确的类型定义,只有满足定义的值才能赋值给属性。
  • 代理:Trait属性值可以代理给其它对象的属性。
  • 监听:Trait属性值发生变化时,可以运行事先指定的函数。
  • 可视化:拥有Trait属性的对象可以很方便地生成编辑Trait属性的界面。

下面的例子展示了Trait属性的上述功能。

06-traits/traits_demo.py

演示Trait属性的五项功能
class Parent ( HasTraits ):
    # 初始化: last_name被初始化为'Zhang'
    last_name = Str( 'Zhang' ) #

class Child ( HasTraits ):
    age = Int

    # 验证: father属性的值必须是Parent类的实例
    father = Instance( Parent ) #

    # 代理: Child的实例的last_name属性代理给其father属性的last_name
    last_name = Delegate( 'father' ) #

    # 监听: 当age属性的值被修改时,下面的函数将被运行
    def _age_changed ( self, old, new ): #
        print 'Age changed from %s to %s ' % ( old, new )

程序中定义了Parent和Child两个从HasTraits继承的类,我们在IPython中载入这两个类,并分别创建它们的对象p和c:

>>> from traits_demo import Parent, Child
>>> p = Parent()
>>> c = Child()

❶用Str类型了定义Parent对象的last_name属性是一个字符串,并且它的缺省值为’Zhang’:

>>> p.last_name
'Zhang'

❷用Instance类型定义Child对象的father属性是Parent类的实例,而father属性的缺省值为None。

如果Parent类在Child类之后定义,可以用字符串表示类:father = Instance(‘Parent’)。

❸通过Delegate类型,为Child对象创建了一个代理属性last_name。代理功能将使得c.last_name和c.father.last_name始终拥有相同的值。但是由于还没有设置对象c的father属性,因此无法正确获得对象c的last_name属性:

>>> c.last_name
Traceback (most recent call last):
AttributeError: 'NoneType' object has no attribute 'last_name'

设置了对象c的father属性之后,我们就可以正确获取它的last_name属性了。并且对象c和p的last_name属性将始终保持一致:

>>> c.father = p
>>> c.last_name
'Zhang'
>>> p.last_name = "ZHANG"
>>> c.last_name
'ZHANG'

❹当对象c的age属性值发生变化时将调用其监听函数_age_changed():

>>> c.age = 4
Age changed from 0 to 4

最后,我们调用configure_traits()显示一个修改属性值的对话框,如【图:为Child对象自动生成的属性修改对话框(左)、点Father按钮弹出编辑Parent对象的对话框(右)】(左)所示:

>>> c.configure_traits()
/tech/static/books/scipy/_images//traits_demo.png

为Child对象自动生成的属性修改对话框(左)、点Father按钮弹出编辑Parent对象的对话框(右)

从自动生成的界面可以看到,属性按照其英文名排序,垂直排为一列。由于father属性是Parent类的对象,所以界面中以一个按钮表示它,点此按钮将会弹出一个如【图:为Child对象自动生成的属性修改对话框(左)、点Father按钮弹出编辑Parent对象的对话框(右)】(右)所示的对话框编辑father属性所对应的对象。

如果在编辑Father对象的对话框中修改last_name属性,Child对象的last_name属性也同时被修改,这是因为Child对象的last_name属性是一个代理属性,其值和father.last_name始终保持一致。

还可以调用print_traits()输出所有的Trait属性名和属性值:

>>> c.print_traits()
age:       4
father:    <__main__.Parent object at 0x13B49120>
last_name: u'Zhang'

或调用get()得到一个描述对象所有Trait属性的字典:

>>> c.get()
{'age': 4, 'last_name': u'Zhang', 'father': <__main__.Parent object at 0x13B49120>}

此外还可以调用set()设置Trait属性的值,用set()可以同时配置多个Trait属性:

>>> c.set(age = 6)
Age changed from 4 to 6
<__main__.Child object at 0x13B494B0>

在创建HasTraits的派生类的对象时可以使用关键字参数设置各个Trait属性的值,例如:

>>> c2 = Child(father=p, age=3)

当派生类中定义了__init__()时,在其中必须调用其父类的__init__()方法,否则Trait属性的一些功能将无效。

也许读者会对Trait属性的工作原理感兴趣,下面简单地介绍这些功能是如何实现的。

首先Trait属性本身和普通Python对象的属性是一样的。但是每个Trait属性都有一个CTrait对象与之对应,这个CTrait对象为Trait属性提供了许多额外的功能。可以通过trait(‘属性名’)获得与某个属性相对应的CTrait对象,或者用traits()获得包含所有CTrait对象的字典。下面的语句获得age属性对应的CTrait对象:

>>> c.trait("age")
<enthought.traits.traits.CTrait object at 0x2A010A80>

Trait属性的缺省值保存在与其对应的CTrait对象中:

>>> p.trait("last_name").default
'Zhang'

给Trait属性赋值时的验证工作由CTrait对象的validate()完成。当验证失败时抛出异常,验证成功时则返回所要赋的值。因此validate()还可以对值进行处理后再赋值给属性。下面直接调用father属性所对应的CTrait对象的validate():

>>> c.trait("father").validate(c, "father", 2)
TraitError: The 'father' trait of a Child instance must be a Parent or None,
but a value of 2 <type 'int'> was specified.
>>> c.trait("father").validate(c, "father", p)
<__main__.Parent object at 0x27DB7180>

当Trait属性值被改变时,HasTraits对象的trait_property_changed()[1]会被调用,在此方法中将会调用用户定义的属性监听函数。注意它只调用监听函数,并不会修改属性的值,因此下面的语句将调用_age_changed(),但不会修改age属性的值:

>>> c.trait_property_changed("age", 8, 10)
Age changed from 8 to 10
>>> c.age # age属性值没有发生变化
6

CTrait对象是连接Trait属性和Trait类型的纽带,通过CTrait对象的trait_type属性可以获得定义Trait属性时所使用的Trait类型:

>>> c.trait("age").trait_type
<enthought.traits.trait_types.Int object at 0x2BD1EFD0>
>>> c.trait("father").trait_type
<enthought.traits.trait_types.Instance object at 0x2BD103D0>

Footnotes

[1]trait_property_changed()在HasTraits的父类CHasTraits中定义,CHasTraits是用C语言实现的,可以在“ctraits.c”中找到其源程序。

上一个主题

开发背景

下一个主题

Trait类型对象

本页

loading...