Rian
Rian

Reputation: 55

track (un)succesfull object initialisation

I have a python script that controls different test-instruments (signal generator, amplifier, spectrum analyzer...) to automate a test.

These devices communicate over ethernet or serial with the pc running this python script.

I wrote a class for each device that I use. The script starts with initializing an instance of those classes. Something like this:

multimeter = Multimeter(192.168.1.5,5025)
amplifier = Amplifier(192.168.1.9,5025)
stirrer = Stirrer('COM4',9600)
.....

This can co wrong in many ways (battery is low, device not turned on, cable not connected ... )

It's possible to catch the errors with try/catch - try-except:

try:
    multimeter = Multimeter(192.168.1.5,5025)
    amplifier = Amplifier(192.168.1.9,5025)
    stirrer = Stirrer('COM4',9600)
    .....
 except:
    multimeter.close()
    amplifier.close()
    stirrer.close()

But now the problem is inside the except code block... We are not sure if the initialization of the objects succeeded and if they exist. They may not exist and so we can't call the close() method.

Because creating the instances is just normal sequential code, I know that when creating an instance of one of my classes fails, all the instances of the other classes previous to that line of code succeed. So you can try to create an instance of every class and check if that fails or not, and if it fails closing the connections of all previous objects.

try:
   multimeter = Multimeter(192.168.1.5,5025)
except:
   #problem with the multimeter
   print('error')
try:
   amplifier = Amplifier(192.168.1.9,5025)
except:
   #problem with the amplifier, but we can close the multimeter
   multimeter.close()
try:
   stirrer = Stirrer('COM4',9600)
except: 
    #problem with the stirrer, but we can close the multimeter and the 
     amplifier
    multimeter.close()
    amplifier.close()
....

But I think this is ugly code? In particular when the number of objects (here test instruments grows, this becomes unmanageable. And it's sensitive for errors when you want to add or remove an object... Is there a better way to be sure that all connections are closed? Sockets should be closed on failure so we can assign the ip-address and port to a socket the next time the script is executed. Same with the serial interfaces, if it's not closed, it will raise an error to because you can't connect to a serial interface that already is open...

Upvotes: 0

Views: 27

Answers (2)

Rian
Rian

Reputation: 55

In fact, the solution that I'm suggesting in my question is the easiest way to solve this issue. In the try block, the script tries to initialize the instances one by one.

If you close the objects in the same order that they're created in the try block, then closing the connection will succeed for every test instrument, except for the instruments that where not initialized because of the error that happened in the try block.

(see comments in code snippet)

try:
    multimeter = Multimeter(192.168.1.5,5025) #succes
    amplifier = Amplifier(192.168.1.9,5025) #succes
    stirrer = Stirrer('COM4',9600) # error COM4 is not available --> jump to except
    generator = Generator()  #not initialized because of error in stirrer init
    otherTestInstrument = OtherTestInsrument() #not initialized because of error in stirrer init
    .....
 except:
    multimeter.close()  #initialized in try, so close() works
    amplifier.close()   #initialized in try, so close() works
    stirrer.close()     #probably initialized in try, so close() works probably
    generator.close()   #not initialized, will raise error, but doesn't matter.
    otherTestInstrument.close() #also not initialized. No need to close it too. 

Upvotes: 0

bruno desthuilliers
bruno desthuilliers

Reputation: 77912

Use a container to store already created instruments, and split your code in short, independent, manageable parts:

def create_instruments(defs):
    instruments = {}
    for key, cls, params in instruments_defs:
        try:
            instruments[key] = cls(*params)
        except Exception as e:
            print("failed to instanciate '{}': {}".format(key, e))
            close_instruments(instruments)
            raise
    return instruments

def close_instruments(intruments):
    for key, instrument in intruments.items():
        try:
            instrument.close()
        except Exception as e:
            # just mention it - we can't do much more anyway
            print("got error {} when closing {}".format(e, key))


instruments_defs = [
    #(key, classname, (param1, ...)
    ("multimeter", Multimeter, ("192.168.1.5", 5025)),
    ("amplifier", Amplifier, ("192.168.1.9" ,5025)),
    ("stirrer", Stirrer, ('COM4',9600)),
    ]

instruments = create_instruments(instruments_defs)

You may also want to have a look at context managers (making sure resources are properly released is the main reason of context managers) but it might not necessarily be the best choice here (depends on how you use those objects, how your code is structured etc).

Upvotes: 1

Related Questions