Python Asteroids on GitHub

A test suggests that calling a method from the Timer works just fine. I confess that I find it a bit hard to grok at first.

I wrote this test:

    def test_with_method(self):
        checker = Checker(19)
        another = Checker(9)
        some_value = 31
        timer = Timer(1, checker.set, some_value)
        timer2 = Timer(1, another.set, 21)
        timer.tick(1.1)
        assert checker.happened == 31 + 19
        timer2.tick(1.1)
        assert another.happened == 21 + 9

It uses this class:

class Checker():
    def __init__(self, extra):
        self.happened = 0
        self.extra = extra if extra else 0

    def set(self, value):
        self.happened = value + self.extra
        return True

For the test to pass, the two timers need to be referencing different instances of the class, the one with extra equal to 19, the other with it equal to 9, and with different input values to set, 31 and 21 respectively.

The test runs. What I don’t understand is how it gets the right instance. the only clue it gets is the difference between checker.set and another.set. That should be a function.

Let’s print those.

        print(checker.set)
        print(another.set)

That prints:

<bound method Checker.set of <test_timer.Checker object at 0x1058cd310>>
<bound method Checker.set of <test_timer.Checker object at 0x1058cd2d0>>

So, nice. Obviously a “bound method” is a function that knows the specific object it is bound to. Just what we hoped for, and what the test suggested must have happened. I haven’t found any good documentation on what’s going on, but clearly what happens is that the compiler sees checker.set and checker must surely be known to it at that point, so it assumes checker to be an object and creates the “bound method” as a little package that calls the function, passing the object as the first, self, parameter.

We don’t have to think about it: we just have to know that it works.

Let’s review how we’re doing the zigzag in Saucer:

    def set_zig_timer(self):
        def zig(saucer):
            saucer.velocity = saucer.new_direction() * saucer.direction
            return True

        # noinspection PyAttributeOutsideInit
        self.zig_timer = Timer(u.SAUCER_ZIG_TIME, zig, self)

We can make this more clear, I think, like this:

    def set_zig_timer(self):
        # noinspection PyAttributeOutsideInit
        self.zig_timer = Timer(u.SAUCER_ZIG_TIME, self.zig_zag_action)

    def zig_zag_action(self):
        self.velocity = self.new_direction() * self.direction
        return True

That works as advertised. And it’s definitely nicer to be able to define the action as a method on the object. I just invented the convention of naming them something_action as a reminder to return T/F.

A noticeable improvement! See you next time!