Как я могу отправить уведомление о статическом событии Traits в Список? - PullRequest
6 голосов
/ 04 декабря 2011

Я работаю через презентацию traits от PyCon 2010 .Около 2:30:45 докладчик начинает охватывать уведомлений о событиях черты , которые позволяют (среди прочего) возможность автоматически вызывать подпрограмму в любое время, когда trait изменился.

Я запускаю модифицированную копию примера, который он дал ... В этом испытании я пытаюсь выяснить, могу ли я запускать статическое событие всякий раз, когда я изменяю volume или inputs.

from traits.api import HasTraits, Range, List, Float
import traits
class Amplifier(HasTraits):
    """
    Define an Amplifier (a la Spinal Tap) with Enthought's traits.  Use traits  
    to enforce values boundaries on the Amplifier's objects.  Use events to 
    notify via the console when the volume trait is changed and when new volume 
    traits are added to inputs.
    """
    volume = Range(value=5.0, trait=Float, low=0.0, high=11.0)
    inputs = List(volume)    # I want to fire a static trait event notification
                             # when another volume element is added

    def __init__(self, volume=5.0):
        super(Amplifier, self).__init__()
        self.volume = volume
        self.inputs.append(volume)

    def _volume_changed(self, old, new):
        # 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 _inputs_changed(self, old, new):
        # static event listener for self.inputs
        print "Check it out!!"

if __name__=='__main__':
    spinal_tap = Amplifier()
    spinal_tap.volume = 11.0
    print "DIRECTLY adding a new volume input..."
    spinal_tap.inputs.append(4.0)
    try:
        print "NEGATIVE Test... adding 12.0"
        spinal_tap.inputs.append(12.0)
    except  traits.trait_errors.TraitError:
        print "Test passed"

Когда я запускаю этот скрипт, я вижу This one goes to eleven... so far, we have seen [5.0, 11.0] в выводе консоли, поэтому я знаю, что _volume_changed() срабатывает, когда я назначаю 11.0 для spinal_tap.volume.

Однако я никогда не вижу событий из _inputs_changed().Независимо от того, какой пример я готовлю, я не могу получить List для запуска события.

Это вывод, который я вижу ... обратите внимание, что нет никаких доказательствчто _inputs_changed() когда-либо срабатывает.

[mpenning@Bucksnort ~]$ python spinaltap.py
This one goes to eleven... so far, we have seen [5.0, 11.0]
DIRECTLY adding a new volume input...
NEGATIVE Test... adding 12.0
Test passed
[mpenning@Bucksnort ~]$

Я запустил это как под Python2.6 / Cygwin / Windows 7 и Python 2.5 / Linux (все с использованием traits версии 4.0.0 что я easy_install прямо с сайта Enthought ).Результаты одинаковы, независимо от того, что я пробовал до сих пор.

Должен ли List быть в состоянии вызвать статическое событие при использовании черт?Если так, я делаю что-то не так?

Ответы [ 2 ]

5 голосов
/ 04 декабря 2011

После просмотра их юнит-тестов, я нашел тест на Dict черты в охвате юнит-тестов событий enthought ... похоже, когда у вас есть контейнер типа Dict или List, который вам нужно настроить метод прослушивания магических событий следующим образом:

## Broken method definition: def _inputs_changed(self, old, new):
# container event static listeners must be in the form of _foo_items_changed()
def _inputs_items_changed(self, old, new):
    # static event listener for self.inputs
    if len(new.added) > 0:
        print "Check it out, we added %s to self.items" % new.added
    elif len(new.removed) > 0:
        print "Check it out, we removed %s from self.items" % new.removed

Кроме того, я также обнаружил, что для декоратора on_trait_change (используемого для динамического уведомления о событиях traits) требуется аналогичная номенклатура, если вы вызываете его с traits.api.List или traits.api.Dict ..., поэтому я мог бы также написать код выше как:

from traits.api import on_trait_change
# ...
@on_trait_change('inputs_items')
def something_changed(self, name, new):
    # static event listener for self.inputs
    if len(new.added) > 0:
        print "Check it out, we added %s to self.items" % new.added
    elif len(new.removed) > 0:
        print "Check it out, we removed %s from self.items" % new.removed

В любом случае, когда я запускаю код, я получаю ожидаемый вывод:

[mpenning@Bucksnort ~]$ python spinaltap.py
Check it out, we added [5.0] to self.items
Check it out, we added [11.0] to self.items
This one goes to eleven... so far, we have seen [5.0, 11.0]
DIRECTLY adding a new volume input...
Check it out, we added [4.0] to self.items
NEGATIVE Test... adding 12.0
Test passed
[mpenning@Bucksnort ~]$
1 голос
/ 30 октября 2013

Поскольку это также недавно меня поразило, я только что подтвердил ответ Майка Пеннингтона с чертами 4.2.1. Похоже, существует различие между изменениями самой черты Списка (такими как присвоение ей нового списка) и изменениями членства Списка (такими как добавление или настройка по индексу). Первый использует то же имя, что и черта (например, inputs), тогда как второй использует суффикс "_items". Этот пример, кажется, демонстрирует это:

from traits.api import Float, HasTraits, Instance, List

class Part(HasTraits):
    costs = List(Float)

    # called when the actual List trait changes:
    def _costs_changed(self, old, new):
        print("Part::_costs_changed %s -> %s" % (str(old), str(new)))

    # called when the contents of the List trait changes:
    def _costs_items_changed(self, old, new):
        print("Part::_costs_changed %s -> %s" % (str(old), str(new)))

class Widget(HasTraits):
    part = Instance(Part)

    def __init__(self):
        self.part = Part()
        self.part.on_trait_change(self.update_costs, 'costs')
        self.part.on_trait_change(self.update_costs_items, 'costs_items')

    def update_costs(self, name, new):
        print("update_costs: %s = %s" % (name, str(new),))

    def update_costs_items(self, name, new):
        print("update_costs_items: %s = %s" % (name, str(new),))

w = Widget()

w.part.costs = [ 1.0, 2.0, 3.0 ]
# Part::_costs_changed [] -> [1.0, 2.0, 3.0]
# update_costs: costs = [1.0, 2.0, 3.0]

w.part.costs = [ 1.0, 2.0, 3.1 ]
# Part::_costs_changed [1.0, 2.0, 3.0] -> [1.0, 2.0, 3.1]
# update_costs: costs = [1.0, 2.0, 3.1]

w.part.costs[0] = 5.0
# Part::_costs_changed <undefined> -> <traits.trait_handlers.TraitListEvent object at 0x1007bd810>
# update_costs_items: costs_items = <traits.trait_handlers.TraitListEvent object at 0x1007bd810>

w.part.costs.append(4.0)
# Part::_costs_changed <undefined> -> <traits.trait_handlers.TraitListEvent object at 0x1007bd810>
# update_costs_items: costs_items = <traits.trait_handlers.TraitListEvent object at 0x1007bd810>

На это поведение намекает документация здесь .

Однако, если используется расширенное имя, кажется, возможно, что тот же обработчик вызывается при изменении всего членства в списке или :

from traits.api import Float, HasTraits, Instance, List

class Part(HasTraits):
    costs = List(Float)

def _costs_changed(self, old, new):
    print("_costs_changed %s -> %s" % (str(old), str(new)))

def _costs_items_changed(self, old, new):
    print("_costs_items_changed %s -> %s" % (str(old), str(new)))

class Widget(HasTraits):
    part = Instance(Part)

    def __init__(self):
        self.part = Part()
        self.part.on_trait_change(self.update_costs, 'costs[]')  # <-- extended name

    def update_costs(self, name, new):
        print("update_costs: %s = %s" % (name, str(new),))

w = Widget()

w.part.costs = [ 1.0, 2.0, 3.0 ]
# _costs_changed [] -> [1.0, 2.0, 3.0]
# update_costs: costs = [1.0, 2.0, 3.0]

w.part.costs = [ 1.0, 2.0, 3.1 ]
# _costs_changed [1.0, 2.0, 3.0] -> [1.0, 2.0, 3.1]
# update_costs: costs = [1.0, 2.0, 3.1]

w.part.costs[0] = 5.0
# _costs_items_changed <undefined> -> <traits.trait_handlers.TraitListEvent object at 0x1007c6f90>
# update_costs: costs_items = [5.0]

w.part.costs.append(4.0)
# _costs_items_changed <undefined> -> <traits.trait_handlers.TraitListEvent object at 0x1007c6f90>
# update_costs: costs_items = [4.0]

В этом случае параметр name обработчика update_costs можно использовать для различения изменения самого контейнера или изменения одного элемента в контейнере.

...