Python作为一种动态编程语言,它的变量没有类型,这种灵活性给快速开发带来很多便利,不过它也不是没有缺点。Traits库的一个很重要的目的就是为了解决这些缺点所带来的问题。
Traits库最初是为了开发Chaco(一个2D绘图库)而设计的,绘图库中有很多绘图用的对象,每个对象都有很多例如线型、颜色、字体之类的属性。为了方便用户使用,每个属性可以允许多种形式的值。例如,颜色属性可以是
• `red’
• 0xff0000
• (255, 0,0)
也就是说可以用字符串、整数、组元等类型的值表达颜色,但是颜色属性虽然可以接受多样的值,却不是能接受所有的值,比如’‘abc’’、0.5等等就不能很好地表示颜色。而且虽然为了方便用户使用,对外的接口可以接受各种各样形式的值,但是在内部必须有一个统一的表达方式来简化程序的实现。
用Trait属性可以很好地解决这样的问题:
• 它可以接受能表示颜色的各种类型的值
• 当给它赋值为不能表达颜色的值时,它能够立即捕捉到错误,并且提供一个有用的错误报告,告诉用户它能够接受什么样的值
• 它提供一个内部的标准的颜色表达方式
trait为Python对象的属性增加了类型定义的功能,此外还提供了如下的额外功能:
• 初始化:每个trait属性都定义有自己的缺省值,这个缺省值用来初始化属性
• 验证:基于trait的属性都有明确的类型定义,只有满足定义的值才能赋值给属性
• 委托:trait属性的值可以委托给其他对象的属性
• 监听:trait属性的值的改变可以触发指定的函数的运行
• 可视化:拥有trait属性的对象可以很方便地提供一个用户界面交互式地改变trait属性的值
如下例子展示了trait所提供的这五项能力:
from enthought.traits.api import Delegate, HasTraits, Instance, Int, Str
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 )
下面用这两个类创建两个实例:
>>> p = Parent()
>>> c = Child()
设置father属性之后,我们就可以得到c的last_name:
>>> c.father = p
>>> c.last_name
'Zhang'
调用configure_traits:
c.configure_traits()
True
弹出如此说对话框,用户可以通过它修改c的trait属性:
可以看到属性按照其英文名排序,垂直排为一列。由于father属性是Parent类的实例,所以它给我们一个按钮,点此按钮出现下面的设置father对象的tratis属性的对话框:
在上面这个对话框中修改father的Last name,可以看到child的Last name属性也随之发生变化。
我们可以调用print_traits方法输出所有的trait属性与其值:
c.print_traits()
调用get方法获得一个描述对象所有trait属性的dict:
c.get()
此外还可以调用set方法设置trait属性的值,set方法可以同时配置多个trait的属性:
c.set(age = 6)
##3.动态添加Trait属性
前面介绍的方法都是在类的定义中声明Trait属性,在类的实例中使用Trait属性。由于Python是动态语言,因此Traits库也提供了为某个特定的实例添加Trait属性的方法。
下面的例子,直接产生HasTraits类的一个实例a, 然后调用其add_trait方法动态地为a添加一个名为x的Trait属性,其类型为Float,初始值为3.0
>>> from enthought.traits.api import *
>>> a = HasTraits()
>>> a.add_trait("x", Float(3.0))
>>> a.x
3.0
接下来再创建一个HasTraits类的实例b,用add_trait方法为b添加一个属性a,指定其类型为HasTraits类的实例。然后把实例a赋值给实例b的属性a:b.a。
>>> b = HasTraits()
>>> b.add_trait("a", Instance(HasTraits))
>>> b.a =a
然后为实例b添加一个类型为Delegate(代理)的属性y,它是b的属性a所表示的实例的属性x的代理,即b.y是b.a.x的代理。注意我们在用Delegate声明代理时,第一个参数b的一个属性名’‘a’’,第二个参数是是此属性的属性名’‘x’’,modify=True表示可以通过b.y修改b.a.x的值。我们看到当将b.y的值改为10的时候,a.x的值也同时改变了。
>>> b.add_trait("y", Delegate("a", "x", modify=True))
>>> b.y
3.0
>>> b.y = 10
>>> a.x
10
标准的Python提供了Property功能,Property看起来像对象的一个成员变量,但是在获取它的值或者给它赋值的时候实际上是调用了相应的函数。Traits也提供了类似的功能。让我们先来看一个例子:
# -*- coding: utf-8 -*-
# filename: traits_property.py
from enthought.traits.api import HasTraits, Float, Property, cached_property
class Rectangle(HasTraits):
width = Float(1.0)
height = Float(2.0)
#area是一个属性,当width,height的值变化时,它对应的_get_area函数将被调用area = Property(depends_on=['width', 'height'])
# 通过cached_property decorator缓存_get_area函数的输出
@cached_property
def _get_area(self):
"""
area的get函数,注意此函数名和对应的Proerty名的关系
"""
print 'recalculating'
return self.width * self.height
在Rectangle类定义中,使用Property()定义了一个area属性。Traits所提供的Property和标准Python的有所不同,Traits中根据属性名直接决定了它的访问函数,当用户读取area值时,将得到_get_area函数的返回值;而设置area的值时,_set_area函数将被调用。此外,通过关键字参数depends_on,指定当width和height属性变化时自动计算area属性。
在 _get_area函数用@cached_property进行修饰,使得 _get_area函数的返回值将被缓存,除非width和height的值发生变化,否则将一直使用缓存的值。下面我们来看看Rectangle的用法。在traits_property.py的文件夹下,启动IPython -wthread:
>>> run traits_property.py
>>> r = Rectangle()
>>> r.area # <-- 第一次取得area,需要进行运算
recalculating
2.0
>>> r.width = 10
>>> r.area # <--修改width之后,取得area,需要进行计算
recalculating
20.0
>>> r.area # <--width和height都没有发生变化,因此直接返回缓存值,没有重新计算
20
g如果我们调用r.edit_traits(),就会看到depends_on的强大功能了。为了更加有趣一些,这里连续调用两次edittraits,弹出两个编辑界面:
>>> r.edit_traits()
>>> r.edit_traits()
然后修改任何一个界面中的width或者height属性,你可以注意到在输入数值的同时,两个界面中的Area,Height和Width等各个文本框同时更新,每次键盘按键都会调用_get_area函数。此时在IPython窗口修改width的值的话,也会调用_get_area函数。
##5.Trait属性监听
HasTraits类的所有对象的所有trait属性都自动支持监听功能。当某个trait属性值发生变化时,HasTraits对象会通知所有监听此属性的函数。
监听函数分为静态和动态两种。
静态监听函数的参数有如下几种形式:
• _age_changed(self)
• _age_changed(self, new)
• _age_changed(self, old, new)
• _age_changed(self, name, old, new)
动态监听函数的参数有如下几种形式:
• observer()
• ovserver(new)
• ovserver(name, new)
• ovserver(obj, name, new)
• ovserver(obj, name, old, new)
其中obj表示属性发生变化的对象,name为发生改变的属性名,old为改变前的值,new为现在值。动态监听函数不但可是普通函数,还可以是某个对象的方法。