神奇的traitlets(赋予PY类属性修改后,自动更改事件)
最近在读一个英伟达库的代码,读到这个的使用法,研究了一下觉得很新奇。
在我们学习py的第一天就是知道它是一个动态的语言,我相信很多人学了很久也不知道动态到底动在哪里,简单的说一下就是创建变量的时候,直接用一个名字和你目标量一连就好,你什么也不用管。一切都是“智能的”,但是这样的便利就会引发一种错误。当你的一个类属性里面的一个变量必须为int时,你缺给了一个strings给它。会怎么样?我也不知道,至少程序是会罢工吧~那一种比较容易想到的做法是在进行赋值之前做一些类型检查。但是一个还好,未来100个呢?我觉得会烦死吧。这就引入了我们本文这个Traitlets。
https://traitlets.readthedocs.io/en/stable/trait_types.html
https://github.com/ipython/traitlets
不多说,直接摔个doc上来,看我文不爽的自己去原味的。
那这个库的作用有什么呢?
Traitlets 允许Python自定义类拥有类型检查、动态计算默认值和Change回调这三种特性。主要是针对自定义的class。而且只需要继承HasTraits即可让自定义的类获得这一系列特性。
类型检查与静态默认值
from traitlets.traitlets import HasTraits
from traitlets import Int,Unicode
class Student(HasTraits):
age = Int()
dada = Unicode("我")
# 这个是将int已经导入
import traitlets
import threading
import numpy as np
# 这个是一个camera的class
class Camera(traitlets.HasTraits):
# 这个地方是对一些参数的保护写的代码
value = traitlets.Any()
width = traitlets.Integer(default_value=224)
height = traitlets.Integer(default_value=224)
format = traitlets.Unicode(default_value='bgr8')
running = traitlets.Bool(default_value=False)
# 而这个是用class。mathmod这样的写法
在以上的代码里面都有体现,就是使用之前,你的类一定要继承一下,然后就是静态默认值,直接写出来就好。完成对你在意量的保护。
在上面,第一个代码里面,你的age其实是一个类属性,但在创建对象时,traitlets已经帮我们创建了同名的示例属性,所以,我们可以放心使用age属性,而不用担心修改的是类属性。
像这个代码里面也有
动态计算默认值
通过@default修饰器将类变量的默认值设置为动态生成:
import getpass
from traitlets.traitlets import HasTraits
from traitlets import Int, Unicode, default
class Identity(HasTraits):
username = Unicode()
@default('username')
def _default_username(self):
return getpass.getuser()
使用default装饰器,username属性的值由下面的def内_default_username来获得,注意_default_username方法只会被执行一次。
观察者模式,属性修改后,用自己的函数更改事件
如果前面的用法是毛毛雨的话,那这个就是瓢泼大雨了。在开始bb之前,我们说下什么是观察者模式。我不太喜欢书中的定义,太装A++(自己思考)。我喜欢自己下定义,模式这个你就理解成一种固定的做法就好。关键是观察者,我平时解释的是用一种视角来说明。观察者就好像视角是在上帝端,或是有个看门狗在监督这个状态。或者就是你找了一个东西来盯着你的这个状态,改变就做点什么。而且有点回调的味道,其实也就是个回调,你的程序状态发生改变,然后好像有个后台的程序在读取到这种改变去做点什么。这里通过@observe修饰器监视类变量的变动:
先看我的箭头
traitlets.observe('running')
# 监视类变量的改动,监视了running这个值
def _on_running(self, change):
# change是我们在捕获到改动事件后做出的反应
if change['new'] and not change['old']:
# transition from not running -> running
# 转换冲未运行到运行
self._running = True
#将这里的运行标志更新
self.thread = threading.Thread(target=self._capture_frames)
# 这里开启了一个新的线程去干活
#这个函数在上面有定义
self.thread.start()@
#开始运行
elif change['old'] and not change['new']:
# transition from running -> not running
# 转换从运行中到停止运行
self._running = False
self.thread.join()
我直接上我的代码,注释写的很清晰了。就是监控上面的bool量。
其中这个change是个字典:
{
'owner': object, # HasTraits instance
'new': 1, # the new value
'old': 0, # the old value
'name': "bar", # The name of the changed trait
'type': 'change', # The event type of the notification, usually 'change'
}
{
“所有者”:对象,#HasTraits实例
“new”:1,#新值
“old”:0,#旧值
“name”:“bar”,#更改特征的名称
'type':'change',#通知的事件类型,通常为'change'
}
继续说,还有一个功能就是让一个属性在一个范围内变化
交叉验证器这个名字要记住哦~
如果对某个属性有取值范围的限定,或者其他要求,那么可以对这个属性值进行验证
from traitlets import HasTraits, TraitError, Int, Bool, validate
class Parity(HasTraits):
value = Int()
parity = Int()
@validate('value')
def _valid_value(self, proposal):
if proposal['value'] % 2 != self.parity:
raise TraitError('value and parity should be consistent')
return proposal['value']
@validate('parity')
def _valid_parity(self, proposal):
parity = proposal['value']
if parity not in [0, 1]:
raise TraitError('parity should be 0 or 1')
if self.value % 2 != parity:
raise TraitError('value and parity should be consistent')
return proposal['value']
parity_check = Parity(value=2)
# Changing required parity and value together while holding cross validation
with parity_check.hold_trait_notifications():
parity_check.value = 1
parity_check.parity = 1
代码取自demo,是靠奇数偶数来判断的。
建议自定义交叉验证器不要修改HasTraits实例的状态。
http://www.coolpython.net/informal_essay/20-04/traitlets.html
这里引一段这位老哥写的东西。
@validate('age')
def _valid_age(self, proposal):
age = proposal['value']
if age < 13 or age > 16:
raise TraitError('学生年龄异常')
return age
这个就写的很简洁明了。
当然了,我用的功能只有这么多,更多的功能可以去doc了~