#!/usr/bin/env python # coding: utf-8 # In[10]: get_ipython().run_line_magic('matplotlib', 'inline') import numpy as np # >作者 : *Didrik Pinte* # # Traits项目允许你可以向Python项目属性方便的添加验证、初始化、委托、通知和图形化界面。 # # 在这个教程中,我们将研究Traits工具包并且学习如何动态减少你所写的锅炉片代码,进行快速的GUI应用开发,以及理解Enthought工具箱中其他部分的想法。 # # Traits和Enthought工具箱是基于BSD-style证书的开源项目。 # # --- # **目标受众** # # Python中高级程序员 # --- # # --- # **要求** # # - [wxPython](http://www.wxpython.org/)、[PyQt](https://riverbankcomputing.com/software/pyqt/intro)或[PySide](https://pyside.github.io/docs/pyside/)之一 # - Numpy和Scipy # - [Enthought工具箱](http://code.enthought.com/projects) # - 所有需要的软件都可以通过安装[EPD免费版](https://store.enthought.com/)来获得 # --- # # --- # # **教程内容** # - 介绍 # - 例子 # - Traits是什么 # - 初始化 # - 验证 # - 文档 # - 可视化: 打开一个对话框 # - 推迟 # - 通知 # - 一些更高级的特征 # # ## 3.4.1 介绍 # # Enthought工具箱可以构建用于数据分析、2D绘图和3D可视化的精密应用框架。这些强力可重用的组块是在BSD-style证书下发布的。 # # Enthought工具箱主要的包是: # # - Traits - 基于组块的方式构建我们的应用。 # - Kiva - 2D原生支持基于路径的rendering、affine转化、alpha混合及其它。 # - Enable - 基于对象的2D绘图画布。 # - Chaco - 绘图工具箱,用于构建复杂的交互2D图像。 # - Mayavi -基于VTK的3D科学数据可视化 # - Envisage - 应用插件框架,用于构建脚本化可扩展的应用 # # ![](http://www.scipy-lectures.org/_images/ETS.jpg) # # 在这篇教程中,我们将关注Traits。 # # ## 3.4.2 例子 # # 在整个这篇教程中,我们将使用基于水资源管理简单案例的一个样例。我们将试着建模一个水坝和水库系统。水库和水坝有下列参数: # # - 名称 # - 水库的最小和最大容量 [$hm^3$] # - 水坝的高度和宽度[$m$] # - 蓄水面积[$km^2$] # - 水压头[$m$] # - 涡轮的动力[$MW$] # - 最小和最大放水量[$m^3/s$] # - 涡轮的效率 # # 水库有一个已知的运转情况。一部分是与基于放水量有关的能量产生。估算水力发电机电力生产的简单公式是$P = \rho hrgk$, 其中 # # - P 以瓦特为单位的功率, # - \rho 是水的密度 ($~1000 kg/m^3$), # - h 是水的高度, # - r 是以每秒立方米为单位的流动率, # - g 重力加速度,9.8 $m/s^2$, # - k 是效率系数,范围从0到1。 # # 年度的电能生产取决于可用的水供给。在一些设施中,水流率在一年中可能差10倍。 # # 运行状态的第二个部分是蓄水量,蓄水量(storage)依赖于控制和非控制参数: # # $storage_{t+1} = storage_t + inflows - release - spillage - irrigation$ # # --- # 本教程中使用的数据不是真实的,可能甚至在现实中没有意义。 # # --- # # ## 3.4.3 Traits是什么 # # trait是可以用于常规Python对象属性的类型定义,给出属性的一些额外特性: # # - 标准化: # - 初始化 # - 验证 # - 推迟 # - 通知 # - 可视化 # - 文档 # # 类可以自由混合基于trait的属性与通用Python属性,或者选择允许在这个类中只使用固定的或开放的trait属性集。类定义的Trait属性自动继承自由这个类衍生的其他子类。 # # 创建一个traits类的常用方式是通过扩展**HasTraits**基础类,并且定义类的traits : # In[1]: from traits.api import HasTraits, Str, Float class Reservoir(HasTraits): name = Str 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]: reservoir = Reservoir(name='Lac de Vouglans', max_storage=605) # ### 3.4.3.1 初始化 # # 所有的traits都有一个默认值来初始化变量。例如,基础python类型有如下的trait等价物: # # # |Trait|Python类型|内置默认值| # |-----|-----------|----------------------| # |Bool|Boolean|False| # |Complex|Complex number|0+0j| # |Float|Floating point number|0.0| # |Int|Plain integer|0| # |Long|Long integer|0L| # |Str|String|''| # |Unicode|Unicode|u''| # # 存在很多其他预定义的trait类型: Array, Enum, Range, Event, Dict, List, Color, Set, Expression, Code, Callable, Type, Tuple, etc。 # # 自定义默认值可以在代码中定义: # In[3]: from traits.api import HasTraits, Str, Float class Reservoir(HasTraits): name = Str max_storage = Float(100) reservoir = Reservoir(name='Lac de Vouglans') # --- # 复杂初始化 # # 当一个trait需要复杂的初始化时,可以实施\_XXX\_默认魔法方法。当调用XXX trait时,它会被懒惰的调用。例如: # # --- # In[4]: def _name_default(self): """ Complex initialisation of the reservoir name. """ return 'Undefined' # ### 3.4.3.2 验证 # # 当用户试图设置trait的内容时,每一个trait都会被验证: # In[5]: reservoir = Reservoir(name='Lac de Vouglans', max_storage=605) reservoir.max_storage = '230' # ### 3.4.3.3 文档 # # 从本质上说,所有的traits都提供关于模型自身的文档。创建类的声明方式使它是自解释的: # In[6]: from traits.api import HasTraits, Str, Float class Reservoir(HasTraits): name = Str max_storage = Float(100) # trait的**desc**元数据可以用来提供关于trait更多的描述信息: # In[7]: from traits.api import HasTraits, Str, Float class Reservoir(HasTraits): name = Str max_storage = Float(100, desc='Maximal storage [hm3]') # 现在让我们来定义完整的reservoir类: # In[8]: from traits.api import HasTraits, Str, Float, Range class Reservoir(HasTraits): name = Str max_storage = Float(1e6, desc='Maximal storage [hm3]') max_release = Float(10, desc='Maximal release [m3/s]') head = Float(10, desc='Hydraulic head [m]') efficiency = Range(0, 1.) def energy_production(self, release): ''' Returns the energy production [Wh] for the given release [m3/s] ''' power = 1000 * 9.81 * self.head * release * self.efficiency return power * 3600 if __name__ == '__main__': reservoir = Reservoir( name = 'Project A', max_storage = 30, max_release = 100.0, head = 60, efficiency = 0.8 ) release = 80 print 'Releasing {} m3/s produces {} kWh'.format( release, reservoir.energy_production(release) ) # ### 3.4.3.4 可视化: 打开一个对话框 # # Traits库也关注用户界面,可以弹出一个Reservoir类的默认视图: # In[ ]: reservoir1 = Reservoir() reservoir1.edit_traits() # ![](http://www.scipy-lectures.org/_images/reservoir_default_view.png) # # TraitsUI简化了创建用户界面的方式。HasTraits类上的每一个trait都有一个默认的编辑器,将管理trait在屏幕上显示的方式 (即Range trait显示为一个滑块等)。 # # 与Traits声明方式来创建类的相同渠道,TraitsUI提供了声明的界面来构建用户界面代码: # In[ ]: from traits.api import HasTraits, Str, Float, Range from traitsui.api import View class Reservoir(HasTraits): name = Str max_storage = Float(1e6, desc='Maximal storage [hm3]') max_release = Float(10, desc='Maximal release [m3/s]') head = Float(10, desc='Hydraulic head [m]') efficiency = Range(0, 1.) traits_view = View( 'name', 'max_storage', 'max_release', 'head', 'efficiency', title = 'Reservoir', resizable = True, ) def energy_production(self, release): ''' Returns the energy production [Wh] for the given release [m3/s] ''' power = 1000 * 9.81 * self.head * release * self.efficiency return power * 3600 if __name__ == '__main__': reservoir = Reservoir( name = 'Project A', max_storage = 30, max_release = 100.0, head = 60, efficiency = 0.8 ) reservoir.configure_traits() # ![](http://www.scipy-lectures.org/_images/reservoir_view.png) # # ### 3.4.3.5 推迟 # # 可以将trait定义和它的值推送给另一个对象是Traits的有用的功能。 # In[ ]: from traits.api import HasTraits, Instance, DelegatesTo, Float, Range from reservoir import Reservoir class ReservoirState(HasTraits): """Keeps track of the reservoir state given the initial storage. """ reservoir = Instance(Reservoir, ()) min_storage = Float max_storage = DelegatesTo('reservoir') min_release = Float max_release = DelegatesTo('reservoir') # state attributes storage = Range(low='min_storage', high='max_storage') # control attributes inflows = Float(desc='Inflows [hm3]') release = Range(low='min_release', high='max_release') spillage = Float(desc='Spillage [hm3]') def print_state(self): print 'Storage\tRelease\tInflows\tSpillage' str_format = '\t'.join(['{:7.2f}'for i in range(4)]) print str_format.format(self.storage, self.release, self.inflows, self.spillage) print '-' * 79 if __name__ == '__main__': projectA = Reservoir( name = 'Project A', max_storage = 30, max_release = 100.0, hydraulic_head = 60, efficiency = 0.8 ) state = ReservoirState(reservoir=projectA, storage=10) state.release = 90 state.inflows = 0 state.print_state() print 'How do we update the current storage ?' # 特殊的trait允许用魔法\_xxxx\_fired方法管理事件和触发器函数: # In[ ]: from traits.api import HasTraits, Instance, DelegatesTo, Float, Range, Event from reservoir import Reservoir class ReservoirState(HasTraits): """Keeps track of the reservoir state given the initial storage. For the simplicity of the example, the release is considered in hm3/timestep and not in m3/s. """ reservoir = Instance(Reservoir, ()) min_storage = Float max_storage = DelegatesTo('reservoir') min_release = Float max_release = DelegatesTo('reservoir') # state attributes storage = Range(low='min_storage', high='max_storage') # control attributes inflows = Float(desc='Inflows [hm3]') release = Range(low='min_release', high='max_release') spillage = Float(desc='Spillage [hm3]') update_storage = Event(desc='Updates the storage to the next time step') def _update_storage_fired(self): # update storage state new_storage = self.storage - self.release + self.inflows self.storage = min(new_storage, self.max_storage) overflow = new_storage - self.max_storage self.spillage = max(overflow, 0) def print_state(self): print 'Storage\tRelease\tInflows\tSpillage' str_format = '\t'.join(['{:7.2f}'for i in range(4)]) print str_format.format(self.storage, self.release, self.inflows, self.spillage) print '-' * 79 if __name__ == '__main__': projectA = Reservoir( name = 'Project A', max_storage = 30, max_release = 5.0, hydraulic_head = 60, efficiency = 0.8 ) state = ReservoirState(reservoir=projectA, storage=15) state.release = 5 state.inflows = 0 # release the maximum amount of water during 3 time steps state.update_storage = True state.print_state() state.update_storage = True state.print_state() state.update_storage = True state.print_state() # 对象间的依赖可以自动使用trait**Property**完成。**depends_on**属性表示property其他traits的依赖性。当其他traits改变了,property是无效的。此外,Traits为属性使用魔法函数的名字: # - \_get\_XXX 来获得XXX属性的trait # - \_set\_XXX 来设置XXX属性的trait # In[ ]: from traits.api import HasTraits, Instance, DelegatesTo, Float, Range from traits.api import Property from reservoir import Reservoir class ReservoirState(HasTraits): """Keeps track of the reservoir state given the initial storage. For the simplicity of the example, the release is considered in hm3/timestep and not in m3/s. """ reservoir = Instance(Reservoir, ()) max_storage = DelegatesTo('reservoir') min_release = Float max_release = DelegatesTo('reservoir') # state attributes storage = Property(depends_on='inflows, release') # control attributes inflows = Float(desc='Inflows [hm3]') release = Range(low='min_release', high='max_release') spillage = Property( desc='Spillage [hm3]', depends_on=['storage', 'inflows', 'release'] ) ### Private traits. _storage = Float ### Traits property implementation. def _get_storage(self): new_storage = self._storage - self.release + self.inflows return min(new_storage, self.max_storage) def _set_storage(self, storage_value): self._storage = storage_value def _get_spillage(self): new_storage = self._storage - self.release + self.inflows overflow = new_storage - self.max_storage return max(overflow, 0) def print_state(self): print 'Storage\tRelease\tInflows\tSpillage' str_format = '\t'.join(['{:7.2f}'for i in range(4)]) print str_format.format(self.storage, self.release, self.inflows, self.spillage) print '-' * 79 if __name__ == '__main__': projectA = Reservoir( name = 'Project A', max_storage = 30, max_release = 5, hydraulic_head = 60, efficiency = 0.8 ) state = ReservoirState(reservoir=projectA, storage=25) state.release = 4 state.inflows = 0 state.print_state() # --- # **注意** 缓存属性 # 当访问一个输入没有改变的属性时,大量计算或长时间运行的计算会是个问题。@cached_property修饰器可以用来缓存这个值,并且只有在失效时才会重新计算一次他们。 # # --- # 让我们用ReservoirState的例子来扩展TraitsUI介绍: # In[ ]: from traits.api import HasTraits, Instance, DelegatesTo, Float, Range, Property from traitsui.api import View, Item, Group, VGroup from reservoir import Reservoir class ReservoirState(HasTraits): """Keeps track of the reservoir state given the initial storage. For the simplicity of the example, the release is considered in hm3/timestep and not in m3/s. """ reservoir = Instance(Reservoir, ()) name = DelegatesTo('reservoir') max_storage = DelegatesTo('reservoir') max_release = DelegatesTo('reservoir') min_release = Float # state attributes storage = Property(depends_on='inflows, release') # control attributes inflows = Float(desc='Inflows [hm3]') release = Range(low='min_release', high='max_release') spillage = Property( desc='Spillage [hm3]', depends_on=['storage', 'inflows', 'release'] ) ### Traits view traits_view = View( Group( VGroup(Item('name'), Item('storage'), Item('spillage'), label = 'State', style = 'readonly' ), VGroup(Item('inflows'), Item('release'), label='Control'), ) ) ### Private traits. _storage = Float ### Traits property implementation. def _get_storage(self): new_storage = self._storage - self.release + self.inflows return min(new_storage, self.max_storage) def _set_storage(self, storage_value): self._storage = storage_value def _get_spillage(self): new_storage = self._storage - self.release + self.inflows overflow = new_storage - self.max_storage return max(overflow, 0) def print_state(self): print 'Storage\tRelease\tInflows\tSpillage' str_format = '\t'.join(['{:7.2f}'for i in range(4)]) print str_format.format(self.storage, self.release, self.inflows, self.spillage) print '-' * 79 if __name__ == '__main__': projectA = Reservoir( name = 'Project A', max_storage = 30, max_release = 5, hydraulic_head = 60, efficiency = 0.8 ) state = ReservoirState(reservoir=projectA, storage=25) state.release = 4 state.inflows = 0 state.print_state() state.configure_traits() # ![](http://www.scipy-lectures.org/_images/reservoir_state_view.png) # # 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[ ]: 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.). In the very same vein as the Traits declarative way of creating classes, TraitsUI provides a declarative interface to build user interfaces code: # In[ ]: # In[ ]: # In[ ]: # In[ ]: # In[ ]: # In[ ]: # In[ ]: # In[ ]: # In[ ]: # In[ ]: # In[ ]: # In[ ]: # In[ ]: # In[ ]: # In[ ]: #