Search code examples
pythonbooleanboolean-logic

Is there a way to detect True to False transitions in python?


I am using the code below to detect transitions from False to True. I need separate code that only detects changes from True to False. Is there a way to do this with the current code below?

test = [True, False, False, True, True, False, True, True, False, False, False, True]

class EdgeDetector:
     """Detects False to True transitions on an external signal."""

     def __init__(self, reader, action):
         self.reader = reader
         self.action = action
         self.last_value = reader()    # initialise value

     def test(self):
         new_value = self.reader()
         if new_value and not self.last_value:
         self.action()
         self.last_value = new_value

 # simple self-test
 if __name__ == '__main__':
     vlist = test
     vgen = (v for v in vlist)              # generator for value sequence

     def test_reader():                     # generate test sequence
        value = next(vgen)
        print("Read:", value)
        return value

     def printer():
        print("Phase transition")

    test_subject = EdgeDetector(test_reader, printer)
    for i in range(len(vlist)-1):
         test_subject.test()

Note: This is not my code. I am using the code provided from this thread: How to execute a function once when boolean value changes in python?

I can't post in that thread to ask a follow up question. Any assistance is appreciated.


Solution

  • The problem is here if new_value and not self.last_value:

    So the first part is looking for if new_value has a Truthy value. AND it's looking for if self.last_value has a Falsey value. This gets muddy when you start comparing two different booleans to each other.

    The first part is really looking to see if there is a value in new_value. So we can instead compare it to none. The second part is really looking to see if the two booleans match, so we can just compare them to each other.

    Update the line to this, and it should work for transitions in either direction.

    if new_value is not None and new_value is not self.last_value:

    Edit:

    As per your comments: There are a couple ways we can track what the phase transitions are with out having to look up each one.

    Option One:

    Using one function to track both transition to false from true and to true from false. We can pass the value back to the printer function.

    def test(self):
        new_value = self.reader()
        if new_value is not None and new_value is not self.last_value:
            self.action(new_value) # Note here we are passing a value back
    
        self.last_value = new_value
    

    Now we just add to the printer function to see the changes.

    def printer(transition):
       print(f"Phase transition to: {transition}")
    

    Finalized Code Option 1:

    test = [True, False, False, True, True, False, True, True, False, False, False, True]
    
    class EdgeDetector:
         """Detects False to True transitions on an external signal."""
    
         def __init__(self, reader, action):
             self.reader = reader
             self.action = action
             self.last_value = reader()    # initialize value
    
         def test(self):
             new_value = self.reader()
             if new_value is not None and new_value is not self.last_value:
                 self.action(new_value)
    
             self.last_value = new_value
    
    
    
    # simple self-test
    if __name__ == '__main__':
        vlist = test
        vgen = (v for v in vlist)              # generator for value sequence
    
        def test_reader():                     # generate test sequence
           value = next(vgen)
           print("Read:", value)
           return value
    
        def printer(transition):
           print(f"Phase transition to: {transition}")
    
        test_subject = EdgeDetector(test_reader, printer)
        for i in range(len(vlist)-1):
            test_subject.test()
    

    Option Two:

    This is the way to do it if you want separate code for transitions from false to true and for from true to false.

    First in the EdgeDetector class we need to modify the init as so:

    def __init__(self, reader, action_true, action_false): #Note the second action here
        self.reader = reader
        self.action_true = action_true
        self.action_false = action_false # We set it just like the previous one.
        self.last_value = reader()
    

    Then we can update the test code.

    def test(self):
        new_value = self.reader()
        if new_value is not None and new_value is not self.last_value:
            if new_value:
                self.action_true()
            else:
                self.action_false()
    
        self.last_value = new_value
    

    Then we modify the printer statement and add a second one:

    def printer_true():
        print("Phase transition to true")
        
    def printer_false():
        print("Phase transition to false")
    
    

    Finally the initialization of EdgeDetector needs to be updated.

    test_subject = EdgeDetector(test_reader, printer_true, printer_false)
    

    Finalized Code for Option 2

    test = [True, False, False, True, True, False, True, True, False, False, False, True]
    
    class EdgeDetector:
         """Detects False to True transitions on an external signal."""
    
         def __init__(self, reader, action_true, action_false):
             self.reader = reader
             self.action_true = action_true
             self.action_false = action_false
             self.last_value = reader()    # initialize value
    
         def test(self):
             new_value = self.reader()
             if new_value is not None and new_value is not self.last_value:
                 self.action(new_value)
    
             self.last_value = new_value
    
    
    
    # simple self-test
    if __name__ == '__main__':
        vlist = test
        vgen = (v for v in vlist)              # generator for value sequence
    
        def test_reader():                     # generate test sequence
           value = next(vgen)
           print("Read:", value)
           return value
    
        def printer_true():
           print(f"Phase transition to true")
    
        def printer_false():
           print(f"Phase transition to false")
    
        test_subject = EdgeDetector(test_reader, printer_true, printer_false)
        for i in range(len(vlist)-1):
            test_subject.test()