-
Notifications
You must be signed in to change notification settings - Fork 9
Exceptions
In object-oriented programming we deal with things going wrong by things called "Exceptions". These are essentially special types of objects that your code can "throw" (or "raise" in the case of Python). If your code "throws" an exception then something should "catch" it and deal with it accordingly.
Instrument objects from JISA
can throw exceptions when something goes wrong. For example, it may throw an exception when trying to connect if a connection cannot be established or when trying to take a reading if the instrument takes too long to reply (ie a time-out).
Understanding and being able to deal with exceptions is important for creating robust code.
The most important part of this is understanding how to catch an exception and deal with it. As an example, let's attempt to connect to a Keithley 2600B over TCP-IP and take a voltage measurement:
Java
SMU smu = new K2600B(new TCPIPAddress("192.168.0.5"));
smu.turnOn();
double voltage = smu.getVoltage();
smu.turnOff();
smu.close();
Kotlin
val smu = K2600B(TCPIPAddress("192.168.0.5"))
smu.turnOn()
val voltage = smu.getVoltage()
smu.turnOff()
smu.close()
Python
smu = K2600B(TCPIPAddress("192.168.0.5"))
smu.turnOn()
val voltage = smu.getVoltage()
smu.turnOff()
smu.close()
Calling the K2600B()
constructor like this can throw an exception if the connection fails or is lost during the measurement. To be able to catch such an exception, we must put this code inside a try ... catch
(or try ... except
in Python) block:
Java
try {
SMU smu = new K2600B(new TCPIPAddress("192.168.0.5"));
smu.turnOn();
double voltage = smu.getVoltage();
smu.turnOff();
smu.close();
} catch (Exception e) {
GUI.errorAlert(e.getMessage());
}
Kotlin
try {
val smu = K2600B(TCPIPAddress("192.168.0.5"))
smu.turnOn()
val voltage = smu.getVoltage()
smu.turnOff()
smu.close()
} catch (e: Exception) {
GUI.errorAlert(e.message)
}
Python
try:
smu = K2600B(TCPIPAddress("192.168.0.5"))
smu.turnOn()
val voltage = smu.getVoltage()
smu.turnOff()
smu.close()
except Exception as e:
GUI.errorAlert(e.getMessage())
What this structure does is it tries to run what is inside the try { }
block and if an exception is thrown it will look for a corresponding catch (...)
(or except
) block that matches the type of exception thrown and run that instead.
In our case, we have one catch
block designed to catch exceptions of type Exception
. As it happens, all exception types extend from Exception
so this block will catch all exceptions. However, we could be more specific. Instruments in JISA
can throws an IOException
if something goes wrong with communications or a DeviceException
if there some sort of compatibility issue. Therefore, we can add multiple catch
blocks to catch each different type of exception to produce a more specific error message, as well as leaving an Exception
catch
block to catch anything else that isn't one of our expected types:
Java
try {
...
} catch (IOException e) {
GUI.errorAlert("I/O Error", e.getMessage());
} catch (DeviceException e) {
GUI.errorAlert("Device Error", e.getMessage());
} catch (Exception e) {
GUI.errorAlert("Error", e.getMessage());
}
Kotlin
try {
...
} catch (e: IOException) {
GUI.errorAlert("I/O Error", e.message)
} catch (e: DeviceException) {
GUI.errorAlert("Device Error", e.message)
} catch (e: Exception) {
GUI.errorAlert("Error", e.message)
}
Python
try:
...
except IOException as e:
GUI.errorAlert("I/O Error", e.getMessage())
except DeviceException as e:
GUI.errorAlert("Device Error", e.getMessage())
except Exception as e:
GUI.errorAlert("Error", e.getMessage())
For Java and Kotlin it is important to remember the scope of your variable. Specifically, if we were to put a try ... catch
block around connecting to our SMU like so:
try {
SMU smu = new K2600B(new TCPIPAddress("192.168.0.5"));
} catch (Exception e) {
...
}
// This will fail! smu is not defined outside the try block
smu.turnOn();
try {
val smu = K2600B(TCPIPAddress("192.168.0.5"));
} catch (e: Exception) {
...
}
// This will fail! smu is not defined outside the try block
smu.turnOn()
then we cannot access the variable smu
from outside that try
block since we declared it inside it. Instead, we must declare smu
first, outside the block like so:
Java
SMU smu;
try {
smu = new K2600B(new TCPIPAddress("192.168.0.5"));
} catch (Exception e) {
...
}
// This will now be fine
smu.turnOn();
Kotlin
val smu: SMU
try {
smu = K2600B(TCPIPAddress("192.168.0.5"))
} catch (e: Exception) {
...
}
// This will now be fine
smu.turnOn()
Because Python does not let you declare variables properly (instead only letting you assign them), it has no proper concept of scope. Therefore you would be fine writing:
Python
try:
smu = K2600B(TCPIPAddress("192.168.0.5"))
except Exception as e:
...
smu.turnOn()
The code in your catch
/except
block in this case should do something that stops the programming continuing on to the smu.turnOn()
line (such as return
) since the connection failed and smu
will not be initialised.
void myFunction() {
SMU smu;
try {
smu = new K2600B(new TCPIPAddress("192.168.0.5"));
} catch (Exception e) {
GUI.errorAlert(e.getMessage());
return;
}
// This will now be fine
smu.turnOn();
}
In extension to the try { } catch { }
structure, you can add a finally { }
block. Code in this block will run after that in the try { }
and any catch { }
blocks regardless of whether an exception was thrown or not.
This is often useful to make sure an instrument is properly turned off/disconnected after a measurement, regardless of whether it went wrong.
For example:
Java
try {
smu.turnOn();
for (double v : Util.makeLinearArray(0, 60, 61)) {
smu.setVoltage(v);
}
} catch (Exception e) {
GUI.errorAlert(e.getMessage());
} finally {
smu.turnOff();
}
Kotlin
try {
smu.turnOn()
for (v in Util.makeLinearArray(0, 60, 61)) {
smu.setVoltage(v)
}
} catch (e: Exception) {
GUI.errorAlert(e.message)
} finally {
smu.turnOff()
}
Python
try:
smu.turnOn()
for v in Util.makeLinearArray(0, 60, 61):
smu.setVoltage(v)
except Exception as e:
GUI.errorAlert(e.getMessage())
finally:
smu.turnOff()
In the above example, we make sure that smu.turnOff()
is called at the end regardless of whether the code in the try
block completed successfully or not.
If you wish to throw your own exceptions, you need only use the throw
keyword (or raise
in Python). For instance, if we make a function that returns the result of dividing two numbers, we will want it to throw an exception if the denominator is zero (ie cannot divide by zero).
You can do this by creating an Exception
object, then give it to throw
/raise
. In Java, if your method can throw an exception you need to state which exceptions it can throw in its declaration by adding throws ...
to the end.
Java
double divide(double numerator, double denominator) throws Exception {
if (denominator == 0) {
throw new Exception("You cannot divide by zero!");
}
return numerator / denominator;
}
Kotlin
fun divide(numerator: Double, denominator: Double) : Double {
if (denominator == 0) {
throw Exception("You cannot divide by zero!")
}
return numerator / denominator;
}
Python
def divide(numerator, denominator):
if denominator == 0:
raise Exception("You cannot divide by zero!")
return numerator / denominator;
- Getting Started
- Object Orientation
- Choosing a Language
- Using JISA in Java
- Using JISA in Python
- Using JISA in Kotlin
- Exceptions
- Functions as Objects
- Instrument Basics
- SMUs
- Thermometers (and old TCs)
- PID and Temperature Controllers
- Lock-Ins
- Power Supplies
- Pre-Amplifiers
- Writing New Drivers