How can I fire a Traits static event notification on a List?

2024/9/20 11:48:54

I am working through the traits presentation from PyCon 2010. At about 2:30:45 the presenter starts covering trait event notifications, which allow (among other things) the ability to automatically call a subroutine any time a trait has changed.

I am running a modified copy of the example he gave... In this trial, I am trying to see whether I can fire a static event whenever I make a change to volume or volume_inputs.

# Filename: spinaltap.py
from traits.api import HasTraits, Range, List, Float
import traitsclass Amplifier(HasTraits):"""Define an Amplifier (ref -> Spinal Tap) with Enthought's traits.  Use traitsto enforce values boundaries on the Amplifier's attributes.  Use events tonotify via the console when the volume trait is changed and when new volumetraits are added to inputs."""# Define a volume trait as a Float between 0.0 and 11.0 (inclusive)# see self._volume_changed()volume = Range(value=5.0, trait=Float, low=0.0, high=11.0)# Define an inputs trait as a List() containing volume traitsvolume_inputs = List(volume) # <-- fire a static trait notification# when another volume element is added# see self._volume_inputs_changed()def __init__(self, volume=5.0):super(Amplifier, self).__init__()self.volume = volumeself.volume_inputs.append(volume)def _volume_changed(self, old, new):# This is a static event listener for self.volume#                                     ^^^^^^^^^^^if not (new in self.inputs):self.inputs.append(self.volume)if new == 11.0:print("This one goes to eleven... so far, we have seen", self.inputs)def _volume_inputs_changed(self, old, new):# This is a static event listener for self.volume_inputs#                                     ^^^^^^^^^^^^^^^^^^print("Check it out!!")if __name__=='__main__':spinal_tap = Amplifier()candidate_volume = 4.0spinal_tap.event_fired = Falseprint("- INITIAL_VALUE var volume_inputs = {}".format(spinal_tap.volume_inputs))print("- APPEND a new volume of 4.0")print("    - volume_inputs = {} # BEGIN".format(spinal_tap.volume_inputs))print("    - volume_inputs.append({})".format(candidate_volume))spinal_tap.volume_inputs.append(candidate_volume)print("    - volume_inputs: {} # END".format(spinal_tap.volume_inputs))if spinal_tap.event_fired is False:print("    - Test FAILED: Traits did not fire _volume_inputs_changed()")else:print("    - Test PASSED: Traits fired _volume_inputs_changed()")try:spinal_tap.event_fired = Falseprint("- NEGATIVE Test... try to append 12.0.  This should fail; 12.0 is out of bounds")print("    - volume_inputs: {} # BEGIN".format(spinal_tap.volume_inputs))candidate_volume = 12.0print("    - volume_inputs.append({})".format(candidate_volume))spinal_tap.volume_inputs.append(candidate_volume)print("    - volume_inputs: {} # END".format(spinal_tap.volume_inputs))if spinal_tap.event_fired is False:print("    - Test FAILED: Traits did not fire _volume_inputs_changed()")except  traits.trait_errors.TraitError:print("    - TraitError raised --> HERE <--")print("    - volume_inputs: {} # END".format(spinal_tap.volume_inputs))print("    - Test PASSED: traits correctly raised TraitError instead of appending {}.".format(candidate_volume))

Problem -> I never see any events from _volume_inputs_changed(). No matter what example I cook up, I can't get a List to fire an event.

In the output below, there is no evidence that _volume_inputs_changed() ever fires.

[mpenning@Bucksnort ~]$ python spinaltap.py
- INITIAL_VALUE var volume_inputs = [5.0]
- APPEND a new volume of 4.0- volume_inputs = [5.0] # BEGIN- volume_inputs.append(4.0)- volume_inputs: [5.0, 4.0] # END- Test FAILED: Traits did not fire _volume_inputs_changed()
- NEGATIVE Test... try to append 12.0.  This should fail; 12.0 is out of bounds- volume_inputs: [5.0, 4.0] # BEGIN- volume_inputs.append(12.0)- TraitError raised --> HERE <--- volume_inputs: [5.0, 4.0] # END- Test PASSED: traits correctly raised TraitError instead of appending 12.0.
[mpenning@Bucksnort ~]$

Should a List() be able to fire a static List() event (such as _inputs_changed()) when using traits? If so, am I doing something wrong?

Answer

After browsing their unit tests, I found a test for Dict traits in enthought's event unittest coverage...

When you have a traits container like a traits.api.List() or traits.api.Dict(), you need to set up the magic event listener method name like this:

# Magic event listener for the `traits.api.List()` called self.volume_inputs
def _volume_inputs_items_changed(self, old, new):
def _volume_inputs_items_changed(self, old, new):# This is a static event listener for --> self.volume_inputs <--if len(new.added) > 0:print "Check it out, we added %s to self.items" % new.addedelif len(new.removed) > 0:print "Check it out, we removed %s from self.items" % new.removed

Likewise, I also discovered that the on_trait_change decorator (used for dynamic traits event notification) requires similar nomenclature if you are calling it with a traits.api.List or traits.api.Dict... so I could also write the code above as:

from traits.api import on_trait_change
# ...
@on_trait_change('volume_inputs_items')
def something_changed(self, name, new):# This is a static event listener for --> self.volume_inputs <--if len(new.added) > 0:print "Check it out, we added %s to self.items" % new.addedelif len(new.removed) > 0:print "Check it out, we removed %s from self.items" % new.removed

Either way, when I run the code, I get expected output:

[mpenning@Bucksnort ~]$ python spinaltap.py- --> Firing _volume_inputs_items_changed() <-- check it out!!
- INITIAL_VALUE var volume_inputs = [5.0]
- APPEND a new volume of 4.0- volume_inputs = [5.0] # BEGIN- volume_inputs.append(4.0)- --> Firing _volume_inputs_items_changed() <-- check it out!!- volume_inputs: [5.0, 4.0] # END- Test PASSED: Traits fired _volume_inputs_changed()
- NEGATIVE Test... try to append 12.0.  This should fail; 12.0 is out of bounds- volume_inputs: [5.0, 4.0] # BEGIN- volume_inputs.append(12.0)- TraitError raised --> HERE <--- volume_inputs: [5.0, 4.0] # END- Test PASSED: traits correctly raised TraitError instead of appending 12.0.
[mpenning@Bucksnort ~]$
https://en.xdnf.cn/q/72509.html

Related Q&A

Calculate moving average in numpy array with NaNs

I am trying to calculate the moving average in a large numpy array that contains NaNs. Currently I am using:import numpy as npdef moving_average(a,n=5):ret = np.cumsum(a,dtype=float)ret[n:] = ret[n:]-r…

Python: numpy.insert NaN value

Im trying to insert NaN values to specific indices of a numpy array. I keep getting this error:TypeError: Cannot cast array data from dtype(float64) to dtype(int64) according to the rule safeWhen tryin…

Identify external workbook links using openpyxl

I am trying to identify all cells that contain external workbook references, using openpyxl in Python 3.4. But I am failing. My first try consisted of:def find_external_value(cell): # identifies an e…

3D-Stacked 2D histograms

I have a bunch of 2D histograms (square 2D numpy arrays) that I want to stack in 3D like so:(Image from: Cardenas, Alfredo E., et al. "Unassisted transport of N-acetyl-L-tryptophanamide through me…

Python and mySQLdb error: OperationalError: (1054, Unknown column in where clause)

Hey all, Im getting an error OperationalError: (1054, "Unknown column XX in where clause")Where XX is the value of CLASS in the following codeconn = MySQLdb.connect(host = "localhost&quo…

Best Python GIS library? [closed]

As it currently stands, this question is not a good fit for our Q&A format. We expect answers to be supported by facts, references, or expertise, but this question will likely solicit debate, argum…

Build a class with an attribute in one line

How do I write a one-liner for the following? class MyClass(): content = {} obj = MyClass()

Python Imports, Paths, Directories Modules

Let me start by saying Ive done extensive research over the course of the past week and have not yet found actual answers to these questions - just some fuzzy answers that dont really explain what is g…

Finding location in code for numpy RuntimeWarning

I am getting warnings like these when running numpy on reasonably large pipeline. RuntimeWarning: invalid value encountered in true_divideRuntimeWarning: invalid value encountered in greaterHow do I fi…

Django, Angular, DRF: Authentication to Django backend vs. API

Im building an app with a Django backend, Angular frontend, and a REST API using Django REST Framework for Angular to consume. When I was still working out backend stuff with a vanilla frontend, I used…