3.4.3 Traits是什么

trait是可以用于常规Python对象属性的类型定义,给出属性的一些额外特性:

  • 标准化:
    • 初始化
    • 验证
    • 推迟
  • 通知
  • 可视化
  • 文档

类可以自由混合基于trait的属性与通用Python属性,或者选择允许在这个类中只使用固定的或开放的trait属性集。类定义的Trait属性自动继承自由这个类衍生的其他子类。

创建一个traits类的常用方式是通过扩展HasTraits基础类,并且定义类的traits :

In [1]:

  1. from traits.api import HasTraits, Str, Float
  2. class Reservoir(HasTraits):
  3. name = Str
  4. max_storage = Float

对Traits 3.x用户来说

如果使用Traits 3.x, 你需要调整traits包的命名空间:

  • traits.api应该为enthought.traits.api
  • traitsui.api应该为enthought.traits.ui.api

像这样使用traits类和使用其他Python类一样简单。注意,trait值通过关键词参数传递:

In [2]:

  1. reservoir = Reservoir(name='Lac de Vouglans', max_storage=605)

3.4.3.1 初始化

所有的traits都有一个默认值来初始化变量。例如,基础python类型有如下的trait等价物:

TraitPython类型内置默认值
BoolBooleanFalse
ComplexComplex number0+0j
FloatFloating point number0.0
IntPlain integer0
LongLong integer0L
StrString''
UnicodeUnicodeu''

存在很多其他预定义的trait类型: Array, Enum, Range, Event, Dict, List, Color, Set, Expression, Code, Callable, Type, Tuple, etc。

自定义默认值可以在代码中定义:

In [3]:

  1. from traits.api import HasTraits, Str, Float
  2. class Reservoir(HasTraits):
  3. name = Str
  4. max_storage = Float(100)
  5. reservoir = Reservoir(name='Lac de Vouglans')

复杂初始化

当一个trait需要复杂的初始化时,可以实施XXX默认魔法方法。当调用XXX trait时,它会被懒惰的调用。例如:

In [4]:

  1. def _name_default(self):
  2. """ Complex initialisation of the reservoir name. """
  3. return 'Undefined'

3.4.3.2 验证

当用户试图设置trait的内容时,每一个trait都会被验证:

In [5]:

  1. reservoir = Reservoir(name='Lac de Vouglans', max_storage=605)
  2. reservoir.max_storage = '230'
  1.  ------------- ------------- ------------- ------------- -----------------------
  2. TraitError Traceback (most recent call last)
  3. <ipython-input-5-cbed071af0b9> in <module>()
  4. 1 reservoir = Reservoir(name='Lac de Vouglans', max_storage=605)
  5. 2
  6. ----> 3 reservoir.max_storage = '230'
  7. /Library/Python/2.7/site-packages/traits/trait_handlers.pyc in error(self, object, name, value)
  8. 170 """
  9. 171 raise TraitError( object, name, self.full_info( object, name, value ),
  10. --> 172 value ) 173
  11. 174 def full_info ( self, object, name, value ):
  12. TraitError: The 'max_storage' trait of a Reservoir instance must be a float, but a value of '230' <type 'str'> was specified.

3.4.3.3 文档

从本质上说,所有的traits都提供关于模型自身的文档。创建类的声明方式使它是自解释的:

In [6]:

  1. from traits.api import HasTraits, Str, Float
  2. class Reservoir(HasTraits):
  3. name = Str
  4. max_storage = Float(100)

trait的desc元数据可以用来提供关于trait更多的描述信息:

In [7]:

  1. from traits.api import HasTraits, Str, Float
  2. class Reservoir(HasTraits):
  3. name = Str
  4. max_storage = Float(100, desc='Maximal storage [hm3]')

现在让我们来定义完整的reservoir类:

In [8]:

  1. from traits.api import HasTraits, Str, Float, Range
  2. class Reservoir(HasTraits):
  3. name = Str
  4. max_storage = Float(1e6, desc='Maximal storage [hm3]')
  5. max_release = Float(10, desc='Maximal release [m3/s]')
  6. head = Float(10, desc='Hydraulic head [m]')
  7. efficiency = Range(0, 1.)
  8. def energy_production(self, release):
  9. ''' Returns the energy production [Wh] for the given release [m3/s]
  10. '''
  11. power = 1000 * 9.81 * self.head * release * self.efficiency
  12. return power * 3600
  13. if __name__ == '__main__':
  14. reservoir = Reservoir(
  15. name = 'Project A',
  16. max_storage = 30,
  17. max_release = 100.0,
  18. head = 60,
  19. efficiency = 0.8
  20. )
  21. release = 80
  22. print 'Releasing {} m3/s produces {} kWh'.format(
  23. release, reservoir.energy_production(release)
  24. )
  1. Releasing 80 m3/s produces 1.3561344e+11 kWh

3.4.3.4 可视化: 打开一个对话框

Traits库也关注用户界面,可以弹出一个Reservoir类的默认视图:

In [ ]:

  1. reservoir1 = Reservoir()
  2. reservoir1.edit_traits()

3.4.3 Traits是什么 - 图1

TraitsUI简化了创建用户界面的方式。HasTraits类上的每一个trait都有一个默认的编辑器,将管理trait在屏幕上显示的方式 (即Range trait显示为一个滑块等)。

与Traits声明方式来创建类的相同渠道,TraitsUI提供了声明的界面来构建用户界面代码:

In [ ]:

  1. from traits.api import HasTraits, Str, Float, Range
  2. from traitsui.api import View
  3. class Reservoir(HasTraits):
  4. name = Str
  5. max_storage = Float(1e6, desc='Maximal storage [hm3]')
  6. max_release = Float(10, desc='Maximal release [m3/s]')
  7. head = Float(10, desc='Hydraulic head [m]')
  8. efficiency = Range(0, 1.)
  9. traits_view = View(
  10. 'name', 'max_storage', 'max_release', 'head', 'efficiency',
  11. title = 'Reservoir',
  12. resizable = True,
  13. )
  14. def energy_production(self, release):
  15. ''' Returns the energy production [Wh] for the given release [m3/s]
  16. '''
  17. power = 1000 * 9.81 * self.head * release * self.efficiency
  18. return power * 3600
  19. if __name__ == '__main__':
  20. reservoir = Reservoir(
  21. name = 'Project A',
  22. max_storage = 30,
  23. max_release = 100.0,
  24. head = 60,
  25. efficiency = 0.8
  26. )
  27. reservoir.configure_traits()

3.4.3 Traits是什么 - 图2

3.4.3.5 推迟

可以将trait定义和它的值推送给另一个对象是Traits的有用的功能。

In [ ]:

  1. from traits.api import HasTraits, Instance, DelegatesTo, Float, Range
  2. from reservoir import Reservoir
  3. class ReservoirState(HasTraits):
  4. """Keeps track of the reservoir state given the initial storage.
  5. """
  6. reservoir = Instance(Reservoir, ())
  7. min_storage = Float
  8. max_storage = DelegatesTo('reservoir')
  9. min_release = Float
  10. max_release = DelegatesTo('reservoir')
  11. # state attributes
  12. storage = Range(low='min_storage', high='max_storage')
  13. # control attributes
  14. inflows = Float(desc='Inflows [hm3]')
  15. release = Range(low='min_release', high='max_release')
  16. spillage = Float(desc='Spillage [hm3]')
  17. def print_state(self):
  18. print 'Storage\tRelease\tInflows\tSpillage'
  19. str_format = '\t'.join(['{:7.2f}'for i in range(4)])
  20. print str_format.format(self.storage, self.release, self.inflows,
  21. self.spillage)
  22. print '-' * 79
  23. if __name__ == '__main__':
  24. projectA = Reservoir(
  25. name = 'Project A',
  26. max_storage = 30,
  27. max_release = 100.0,
  28. hydraulic_head = 60,
  29. efficiency = 0.8
  30. )
  31. state = ReservoirState(reservoir=projectA, storage=10)
  32. state.release = 90
  33. state.inflows = 0
  34. state.print_state()
  35. print 'How do we update the current storage ?'

特殊的trait允许用魔法_xxxx_fired方法管理事件和触发器函数:

In [ ]:

  1. from traits.api import HasTraits, Instance, DelegatesTo, Float, Range, Event
  2. from reservoir import Reservoir
  3. class ReservoirState(HasTraits):
  4. """Keeps track of the reservoir state given the initial storage.
  5. For the simplicity of the example, the release is considered in
  6. hm3/timestep and not in m3/s.
  7. """
  8. reservoir = Instance(Reservoir, ())
  9. min_storage = Float
  10. max_storage = DelegatesTo('reservoir')
  11. min_release = Float
  12. max_release = DelegatesTo('reservoir')
  13. # state attributes
  14. storage = Range(low='min_storage', high='max_storage')
  15. # control attributes
  16. inflows = Float(desc='Inflows [hm3]')
  17. release = Range(low='min_release', high='max_release')
  18. spillage = Float(desc='Spillage [hm3]')
  19. update_storage = Event(desc='Updates the storage to the next time step')
  20. def _update_storage_fired(self):
  21. # update storage state
  22. new_storage = self.storage - self.release + self.inflows
  23. self.storage = min(new_storage, self.max_storage)
  24. overflow = new_storage - self.max_storage
  25. self.spillage = max(overflow, 0)
  26. def print_state(self):
  27. print 'Storage\tRelease\tInflows\tSpillage'
  28. str_format = '\t'.join(['{:7.2f}'for i in range(4)])
  29. print str_format.format(self.storage, self.release, self.inflows,
  30. self.spillage)
  31. print '-' * 79
  32. if __name__ == '__main__':
  33. projectA = Reservoir(
  34. name = 'Project A',
  35. max_storage = 30,
  36. max_release = 5.0,
  37. hydraulic_head = 60,
  38. efficiency = 0.8
  39. )
  40. state = ReservoirState(reservoir=projectA, storage=15)
  41. state.release = 5
  42. state.inflows = 0
  43. # release the maximum amount of water during 3 time steps
  44. state.update_storage = True
  45. state.print_state()
  46. state.update_storage = True
  47. state.print_state()
  48. state.update_storage = True
  49. state.print_state()

对象间的依赖可以自动使用traitProperty完成。depends_on属性表示property其他traits的依赖性。当其他traits改变了,property是无效的。此外,Traits为属性使用魔法函数的名字:

  • _get_XXX 来获得XXX属性的trait
  • _set_XXX 来设置XXX属性的trait

In [ ]:

  1. from traits.api import HasTraits, Instance, DelegatesTo, Float, Range
  2. from traits.api import Property
  3. from reservoir import Reservoir
  4. class ReservoirState(HasTraits):
  5. """Keeps track of the reservoir state given the initial storage.
  6. For the simplicity of the example, the release is considered in
  7. hm3/timestep and not in m3/s.
  8. """
  9. reservoir = Instance(Reservoir, ())
  10. max_storage = DelegatesTo('reservoir')
  11. min_release = Float
  12. max_release = DelegatesTo('reservoir')
  13. # state attributes
  14. storage = Property(depends_on='inflows, release')
  15. # control attributes
  16. inflows = Float(desc='Inflows [hm3]')
  17. release = Range(low='min_release', high='max_release')
  18. spillage = Property(
  19. desc='Spillage [hm3]', depends_on=['storage', 'inflows', 'release']
  20. )
  21. ### Private traits.
  22. _storage = Float
  23. ### Traits property implementation.
  24. def _get_storage(self):
  25. new_storage = self._storage - self.release + self.inflows
  26. return min(new_storage, self.max_storage)
  27. def _set_storage(self, storage_value):
  28. self._storage = storage_value
  29. def _get_spillage(self):
  30. new_storage = self._storage - self.release + self.inflows
  31. overflow = new_storage - self.max_storage
  32. return max(overflow, 0)
  33. def print_state(self):
  34. print 'Storage\tRelease\tInflows\tSpillage'
  35. str_format = '\t'.join(['{:7.2f}'for i in range(4)])
  36. print str_format.format(self.storage, self.release, self.inflows,
  37. self.spillage)
  38. print '-' * 79
  39. if __name__ == '__main__':
  40. projectA = Reservoir(
  41. name = 'Project A',
  42. max_storage = 30,
  43. max_release = 5,
  44. hydraulic_head = 60,
  45. efficiency = 0.8
  46. )
  47. state = ReservoirState(reservoir=projectA, storage=25)
  48. state.release = 4
  49. state.inflows = 0
  50. state.print_state()

注意 缓存属性 当访问一个输入没有改变的属性时,[email protected]_property修饰器可以用来缓存这个值,并且只有在失效时才会重新计算一次他们。

让我们用ReservoirState的例子来扩展TraitsUI介绍:

In [ ]:

  1. from traits.api import HasTraits, Instance, DelegatesTo, Float, Range, Property
  2. from traitsui.api import View, Item, Group, VGroup
  3. from reservoir import Reservoir
  4. class ReservoirState(HasTraits):
  5. """Keeps track of the reservoir state given the initial storage.
  6. For the simplicity of the example, the release is considered in
  7. hm3/timestep and not in m3/s.
  8. """
  9. reservoir = Instance(Reservoir, ())
  10. name = DelegatesTo('reservoir')
  11. max_storage = DelegatesTo('reservoir')
  12. max_release = DelegatesTo('reservoir')
  13. min_release = Float
  14. # state attributes
  15. storage = Property(depends_on='inflows, release')
  16. # control attributes
  17. inflows = Float(desc='Inflows [hm3]')
  18. release = Range(low='min_release', high='max_release')
  19. spillage = Property(
  20. desc='Spillage [hm3]', depends_on=['storage', 'inflows', 'release']
  21. )
  22. ### Traits view
  23. traits_view = View(
  24. Group(
  25. VGroup(Item('name'), Item('storage'), Item('spillage'),
  26. label = 'State', style = 'readonly'
  27. ),
  28. VGroup(Item('inflows'), Item('release'), label='Control'),
  29. )
  30. )
  31. ### Private traits.
  32. _storage = Float
  33. ### Traits property implementation.
  34. def _get_storage(self):
  35. new_storage = self._storage - self.release + self.inflows
  36. return min(new_storage, self.max_storage)
  37. def _set_storage(self, storage_value):
  38. self._storage = storage_value
  39. def _get_spillage(self):
  40. new_storage = self._storage - self.release + self.inflows
  41. overflow = new_storage - self.max_storage
  42. return max(overflow, 0)
  43. def print_state(self):
  44. print 'Storage\tRelease\tInflows\tSpillage'
  45. str_format = '\t'.join(['{:7.2f}'for i in range(4)])
  46. print str_format.format(self.storage, self.release, self.inflows,
  47. self.spillage)
  48. print '-' * 79
  49. if __name__ == '__main__':
  50. projectA = Reservoir(
  51. name = 'Project A',
  52. max_storage = 30,
  53. max_release = 5,
  54. hydraulic_head = 60,
  55. efficiency = 0.8
  56. )
  57. state = ReservoirState(reservoir=projectA, storage=25)
  58. state.release = 4
  59. state.inflows = 0
  60. state.print_state()
  61. state.configure_traits()

3.4.3 Traits是什么 - 图3

Some use cases need the delegation mechanism to be broken by the user when setting the value of the trait. The PrototypeFrom trait implements this behaviour.

In [ ]:

  1. TraitsUI simplifies the way user interfaces are created. Every trait on a HasTraits class has a default editor that will manage the way the trait is rendered to the screen (e.g. the Range trait is displayed as a slider, etc.).
  2. In the very same vein as the Traits declarative way of creating classes, TraitsUI provides a declarative interface to build user interfaces code: