这里发生的是Traits有两种不同的处理通知的方式:静态通知器和动态通知器.
静态通知器(例如由特别命名的_ * _ changed()方法创建的那些)相当轻量级:实例上的每个特征都有一个t上的通知符列表,它们基本上是带有轻量级包装器的函数或方法.
动态通知程序(例如使用on_trait_change()和extended trait name conventions作为[]创建的动态通知程序功能强大且灵活,但因此它们的权重更大.特别是,除了它们创建的包装器对象之外,它们还是还创建了扩展特征名称和处理程序对象的解析表示,其中一些是反过来的HasTraits子类实例.
因此,即使对于像[]这样的简单表达式,也会创建相当数量的新Python对象,并且必须为每个实例上的每个on_trait_change侦听器分别创建这些对象,以正确处理像实例特征这样的角落情况.相关代码如下:https://github.com/enthought/traits/blob/master/traits/has_traits.py#L2330
根据报告的数字,您看到的内存使用量的大部分差异在于为每个实例和每个on_trait_change装饰器创建此动态侦听器基础结构.
值得注意的是,在使用简单特征名称的情况下on_trait_change存在短路,在这种情况下,它会生成静态特征通知程序而不是动态通知程序.所以,如果你要写一些类似的东西:
class FooSimpleDecorator(HasTraits):
a = List(Int)
@on_trait_change('a')
def a_updated(self):
pass
@on_trait_change('a_items')
def a_items_updated(self):
pass
你应该看到与特别命名的方法类似的内存性能.
要回答有关“为什么使用on_trait_change”的重新提出的问题,在FooDecorator中,如果您对列表或列表中任何项目的更改的响应相同,则可以编写一个方法而不是两个方法.这使代码更容易调试和维护,如果您不创建数千个这些对象,那么额外的内存使用量可以忽略不计.
当您考虑更复杂的扩展特征名称模式时,这变得更加重要,其中动态侦听器自动处理更改,否则这些更改需要大量手动(并且容易出错)的代码来连接和从中间对象和特征中删除侦听器.这种方法的强大功能和简单性通常超过了对内存使用的担忧.