# 3.4 Traits:創建交互對話
In?[10]:
```
%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 - 應用插件框架,用于構建腳本化可擴展的應用

在這篇教程中,我們將關注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'
```
```
---------------------------------------------------------------------------
TraitError Traceback (most recent call last)
<ipython-input-5-cbed071af0b9> in <module>()
1 reservoir = Reservoir(name='Lac de Vouglans', max_storage=605)
2
----> 3 reservoir.max_storage = '230'
/Library/Python/2.7/site-packages/traits/trait_handlers.pyc in error(self, object, name, value)
170 """
171 raise TraitError( object, name, self.full_info( object, name, value ),
--> 172 value ) 173
174 def full_info ( self, object, name, value ):
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]:
```
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)
)
```
```
Releasing 80 m3/s produces 1.3561344e+11 kWh
```
### 3.4.3.4 可視化: 打開一個對話框
Traits庫也關注用戶界面,可以彈出一個Reservoir類的默認視圖:
In?[?]:
```
reservoir1 = Reservoir()
reservoir1.edit_traits()
```

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()
```

### 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()
```
**注意** 緩存屬性 當訪問一個輸入沒有改變的屬性時,[[email?protected]](/cdn-cgi/l/email-protection)_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()
```

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:
```
- 介紹
- 1.1 科學計算工具及流程
- 1.2 Python語言
- 1.3 NumPy:創建和操作數值數據
- 1.4 Matplotlib:繪圖
- 1.5 Scipy:高級科學計算
- 1.6 獲取幫助及尋找文檔
- 2.1 Python高級功能(Constructs)
- 2.2 高級Numpy
- 2.3 代碼除錯
- 2.4 代碼優化
- 2.5 SciPy中稀疏矩陣
- 2.6 使用Numpy和Scipy進行圖像操作及處理
- 2.7 數學優化:找到函數的最優解
- 2.8 與C進行交互
- 3.1 Python中的統計學
- 3.2 Sympy:Python中的符號數學
- 3.3 Scikit-image:圖像處理
- 3.4 Traits:創建交互對話
- 3.5 使用Mayavi進行3D作圖
- 3.6 scikit-learn:Python中的機器學習