如何在列表上触发 Traits 静态事件通知?

发布于 2024-12-19 04:40:11 字数 5160 浏览 2 评论 0 原文

我正在研究 traits PyCon 2010 的演示。大约 2:30:45,演示者开始介绍特征事件通知,它允许(除其他外)在 特征已更改。

我正在运行他给出的示例的修改副本...在本次试验中,我试图看看每当我对 volumevolume_inputs< 进行更改时是否可以触发静态事件/代码>。

# Filename: spinaltap.py
from traits.api import HasTraits, Range, List, Float
import traits

class Amplifier(HasTraits):
    """
    Define an Amplifier (ref -> Spinal Tap) with Enthought's traits.  Use traits
    to enforce values boundaries on the Amplifier's attributes.  Use events to
    notify via the console when the volume trait is changed and when new volume
    traits 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 traits
    volume_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 = volume
        self.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.0
    spinal_tap.event_fired = False
    print("- 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 = False
        print("- 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.0
        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()")
    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))

问题->我从未看到来自 _volume_inputs_changed() 的任何事件。无论我编出什么例子,我都无法得到 List 来触发事件。

在下面的输出中,没有证据表明 _volume_inputs_changed() 曾经触发过。

[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 ~]$

应该 List() 使用特征时能够触发静态 List() 事件(例如 _inputs_changed())?如果是这样,我做错了什么吗?

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 traits

class Amplifier(HasTraits):
    """
    Define an Amplifier (ref -> Spinal Tap) with Enthought's traits.  Use traits
    to enforce values boundaries on the Amplifier's attributes.  Use events to
    notify via the console when the volume trait is changed and when new volume
    traits 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 traits
    volume_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 = volume
        self.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.0
    spinal_tap.event_fired = False
    print("- 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 = False
        print("- 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.0
        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()")
    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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

别想她 2024-12-26 04:40:11

浏览完他们的单元测试后,我在 enthought 的 事件单元测试覆盖率...

当您有一个 traits 容器(例如 traits.api.List())时或者traits.api.Dict(),你需要像这样设置魔法事件监听器方法名称:

# 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.added
    elif len(new.removed) > 0:
        print "Check it out, we removed %s from self.items" % new.removed

同样,我还发现on_trait_change装饰器(用于动态如果您使用 traits.api.Listtraits.api.Dict 调用它,traits 事件通知)需要类似的命名法...所以 的代码写为:

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.added
    elif len(new.removed) > 0:
        print "Check it out, we removed %s from self.items" % new.removed

我也可以将上面 这样,当我运行代码时,我得到预期的输出:

[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 ~]$

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.added
    elif 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.added
    elif 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 ~]$
平定天下 2024-12-26 04:40:11

由于这最近也让我感到困惑,我刚刚用 Traits 4.2.1 验证了 Mike Pennington 的答案。对 List 特征本身的更改(例如为其分配新列表)和对 List 的成员身份的更改(例如按索引附加或设置)之间似乎确实存在区别。前者使用与特征相同的名称(例如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>

文档 这里

但是,如果 扩展使用 name 时,当整个列表成员资格更改时,似乎确实可以调用相同处理程序:

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 处理程序可用于区分容器本身的更改或容器内单个项目的更改。

As this also caught me out recently, I've just verified Mike Pennington's answer with Traits 4.2.1. There does seem to be a distinction between changes to the List trait itself (such as assigning a new list to it), and changes to the membership of the List (such as appending or setting by index). The former uses the same name as the trait (e.g. inputs), whereas the latter uses the "_items" suffix. This example seems to demonstrate this:

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>

This behaviour is hinted at in the documentation here.

However if an extended name is used it does seem possible to have the same handler called when the entire list or membership is changed:

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]

In this case, the name parameter of the update_costs handler can be used to differentiate between the container itself changing, or a single item within the container changing.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文