Python使用Traits库实现对象属性
投稿:yin
Python作为一种动态编程语言,它的变量没有类型,这种灵活性给快速开发带来很多便利,不过它也不是没有缺点。Traits库的一个很重要的目的就是为了解决这些缺点所带来的问题。
背景
Traits库最初是为了开发Chaco(一个2D绘图库)而设计的,绘图库中有很多绘图用的对象,每个对象都有很多例如线型、颜色、字体之类的属性。为了方便用户使用,每个属性可以允许多种形式的值。例如,颜色属性可以是'red'、0xff0000、(255, 0, 0)
也就是说可以用字符串、整数、组元等类型的值表达颜色,这样的需求初看起来用Python的无类型变量是一个很好的选择,因为我们可以把各种各样的值赋值给颜色属性。但是颜色属性虽然可以接受多样的值,却不是能接受所有的值,比如"abc"、0.5等等就不能很好地表示颜色。而且虽然为了方便用户使用,对外的接口可以接受各种各样形式的值,但是在内部必须有一个统一的表达方式来简化程序的实现。
用Trait属性可以很好地解决这样的问题:
- 它可以接受能表示颜色的各种类型的值
- 当给它赋值为不能表达颜色的值时,它能够立即捕捉到错误,并且提供一个有用的错误报告,告诉用户它能够接受什么样的值
- 它提供一个内部的标准的颜色表达方式
让我们来看一下使用traits属性表示颜色的例子:
from enthought.traits.api import HasTraits, Color class Circle(HasTraits): color = Color
这个程序从enthought.traits.api中导入我们需要使用的两个对象: HasTraits和Color。所有拥有trait属性的类都需要从HasTraits继承。由于Python的多继承特性,我们很容易将现有的类改为支持trait属性。Color是一个TraitFactory对象,我们在Circle类的定义中用它来声明一个color属性。
Traits是什么
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()
由于没有设置c的father属性,因此无法获得它的last_name属性:
>>> c.last_name Traceback (most recent call last): AttributeError: 'NoneType' object has no attribute 'last_name'
设置father属性之后,我们就可以得到c的last_name了:
>>> c.father = p >>> c.last_name 'Zhang'
设置c的age属性将触发_age_changed方法的执行:
>>> c.age = 4 Age changed from 0 to 4
调用configure_traits:
>>> c.configure_traits() True
弹出一个如下的对话框,用户可以通过它修改c的trait属性,
为Child类自动生成的属性修改对话框
可以看到属性按照其英文名排序,垂直排为一列。由于father属性是Parent类的实例,所以它给我们一个按钮,点此按钮出现下面的设置father对象的tratis属性的对话框
点击Child对话框中的Father按钮之后,弹出编辑father属性的对话框
在上面这个对话框中修改father的Last name,可以看到child的Last name属性也随之发生变化。
我们可以调用print_traits方法输出所有的trait属性与其值:
>>> c.print_traits() age: 4 father: <__main__.Parent object at 0x13B49120> last_name: u'Zhang'
调用get方法获得一个描述对象所有trait属性的dict:
>>> 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>
动态添加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.0
Traits 的使用场景
Traits 的使用场景非常广泛,以下是一些常见的应用场景:
1. 插件系统
Traits 可以用于创建插件系统,使得我们可以为应用程序添加新的功能或模块,而无需修改现有的代码。通过定义一系列的 Traits 类,我们可以轻松地将这些模块组合起来,实现动态的功能扩展。
2. 用户界面开发
Traits 提供了一种简单而强大的方式来创建用户界面。我们可以定义各种用户界面元素的 Traits,然后将它们组合在一起以构建复杂的界面。Traits 还支持属性验证和事件通知,使得我们可以轻松地处理用户输入和交互。
3. 数据分析和科学计算
Traits 提供了一种方便的方式来处理和分析数据。我们可以定义各种数据结构的 Traits,然后使用这些 Traits 来创建和操作数据。Traits 还支持属性验证和默认值,使得数据处理更加健壮和可靠。
总结
Traits 是一个在 Python 中用于实现对象属性的库。Traits 可以帮助我们创建可复用、健壮和可扩展的代码。在软件开发中,Traits 是一种用于描述对象属性的概念。一个 Trait 是一个可重用的代码块,它定义了一组属性和方法,这些属性和方法可以被其他对象继承和使用。Traits 可以用于创建可插拔组件、增加代码的可读性和可维护性,以及提供代码的灵活性和复用性。