When you learn programming, you're usually told that side-effects are not good. This is particularly true for the Petri nets annotations in SNAKES.

Consider this first example:

from snakes.nets import *

class Queue (object) :
    def __init__ (self, values) :
        self.v = list(values)
    def done (self) :
        return not self.v
    def next (self) :
        return self.v.pop(0)
    def __str__ (self) :
        return "Queue(%r)" % self.v
    def __repr__ (self) :
        return "Queue(%r)" % self.v

net = PetriNet("bad")
net.add_place(Place("input"))
net.add_place(Place("output"))
trans = Transition("t", Expression("not x.done()"))
net.add_transition(trans)
net.add_input("input", "t", Variable("x"))
net.add_output("output", "t", Expression("x.next()"))
net.place("input").add(Queue(range(10)))

It declares a class Queue that stores a list from which we can consume one element after the other. Then we put an instance of it with integers from 0 to 9 in a place input, and a transition consumes it to produce the first element picked from the queue in a place output. Let's try it:

>>> execfile("side-effects.py")
>>> net.get_marking() #1
Marking({'input': MultiSet([Queue([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])])})
>>> m = trans.modes()
>>> m #2
[Substitution(x=Queue([1, 2, 3, 4, 5, 6, 7, 8, 9]))]
>>> trans.fire(m[0])
>>> net.get_marking() #3
Marking({'output': MultiSet([2])})

There are problems here: from #1 we see that the next value for the Queue instance should be 0. But after #2 we see that it now starts with 1. Later, from #3 we can seen that 1 has also been skipped and we get 2 instead.

The reason is that expressions are evaluated several time, and side-effects are remembered between two evaluations. This becomes explicit here:

class NotBetter (Queue) :
    def next (self) :
        print("%s.next()" % self)
        return Queue.next(self)

net = PetriNet("not better")
net.add_place(Place("input"))
net.add_place(Place("output"))
trans = Transition("t", Expression("not x.done()"))
net.add_transition(trans)
net.add_input("input", "t", Variable("x"))
net.add_output("output", "t", Expression("x.next()"))
net.place("input").add(NotBetter(range(10)))
print("calling trans.modes()")
m = trans.modes()
print("calling trans.fire()")
trans.fire(m[0])

Let's run it:

$ python side-effects.py 
calling trans.modes()
Queue([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]).next()
calling trans.fire()
Queue([1, 2, 3, 4, 5, 6, 7, 8, 9]).next()
Queue([2, 3, 4, 5, 6, 7, 8, 9]).next()

Printing and more generally any input/output is another kind of side effects. We discover here that method next has been called actually three times: once during trans.modes() and twice during trans.fire(). The reason is as follows (see also here):

While this process is largely suboptimal, it is on the other hand simple to understand and to implement. We could imagine that trans.modes() returns bindings enriched with the information computed for the output arcs, which would avoid so many evaluations. But this would be somehow misleading for the user to get modes with a richer content than expected; and it would also seriously complexify the implementation. Moreover, it wouldn't solve everything. Imagine we want to get rid of method done and implement as follow:

class StillBad (Queue) :
    def next (self) :
        if self.v :
            return self.v.pop(0)
        else :
            return None

net = PetriNet("still bad")
net.add_place(Place("input"))
net.add_place(Place("output"))
trans = Transition("t", Expression("x.next() is not None"))
net.add_transition(trans)
net.add_input("input", "t", Variable("x"))
net.add_output("output", "t", Expression("x.next()"))

Now, x.next() is called twice explicitly. If it had no side-effect, this would be actually correct.

So, the good solution is to have functional Python in nets annotations (i.e., in every Expression instance), in the sense that every annotation is a "pure" expression with no side-effect. If no object if ever mutated, or every mutation occurs on a copy, your nets will not exhibit the kind of wrong behaviour we have observed here above. Take care also to assignments to global/class/modules variables, inputs/outputs, and everything that can be considered as a side-effect.