I'm new to python, and i want to make a simple bank account class, where i want to make a withdraw method, that checks if founds are sufficient and then raises an exception if not.
def withdraw(self, amount):
self.amount -= amount
if amount < self.amount:
raise Exception("Insufficent founds")
print(f"{amount} was withdrawn new balance is {self.amount}")
this is what i have so far, and the logic is working, but i think it would be better with a
try:
# method logic
except Exception:
#handle exception and continue program
my issue is that i can't trigger the exception upon a statement like:
if amount < self.amount:
what do you do in python, when you want to throw an exception upon a certain event, and then continue the program?
The purpose of an exception is for a function to indicate to its caller that it was unable to do whatever thing it is intended to do.
So if you have a function called withdraw, and its purpose is to withdraw funds, if for any reason it cannot withdraw funds, it is appropriate for that function to raise an exception.
If the function identifies a situation where it might be able to withdraw funds, and it performs some extra steps (like prompting the user) and then succeeds, there is no need for any exception. The function still did what it was intended to do. Having the withdraw function take on an extra responsibility of asking for approval seems like a poor design to me, though.
One way to help understand what exceptions you need to raise is to work top down instead of bottom up. That is, start by writing the highest level of what you want your program to do, and then work your way down to the specific details.
If your goal is to simulate an ATM machine, for example, you might start with a top-level function like this:
def main():
while running:
account = select_account()
operation = select_operation()
if operation == 'withdraw':
amount = select_amount()
try:
account.withdraw(amount)
except InsufficientFundsError:
print('Your account has insufficient funds.')
elif operation == 'deposit':
amount = select_amount()
account.deposit(amount)
elif operation == 'done':
running = False
If you start with this "client" code that will use your class, it makes the design for the "service" code (your Account class) much easier. Now you know how you want to be able to use your object, so it's just a matter of writing an object that can be used that way.
When you write a call to a function like account.withdraw(amount), you need to think: How could this fail? What do I want to do if it fails? For example, if the failure reason is that there are insufficient funds, you want to print a message, but you probably don't want to abort the program. But if when you call select_account() the account doesn't exist, then maybe it's OK to fail in a different way. If you fail to handle a possible exception, then the default thing that happens is that your program crashes. That's a good thing: it means that you've found some code that you forgot to write, and if that happens, it's much better for your program to stop running completely than to continue on doing the wrong thing.
A try/except only gets specific errors in python and prevents crashes so the program can keep running (or not if you break). If you don't specify a python's exception it will treat any exception it occurs.
try:
# something
except:
pass #(do nothing)
However if you wan't to treat only specific errors, meaning to catch/ignore only it but others will have other treatments (or even crash on screen) you specify:
div = 0
try:
print(10/div)
except ZeroDivisionError:
print("div can't be zero, change variable")
return # this exits the function
#break # if it is inside a loop
Basically, when you treat an exception the program doesn't stop, it just act like a "known bug" so doesn't crash.
what you could do is exit the function inside your if, but you'd have to do that check before the operation:
def withdraw(self, amount):
if amount < self.amount:
print("Insufficent funds")
return
self.amount -= amount
print(f"{amount} was withdrawn new balance is {self.amount}")
An edit: a better approach would be instead of printing the "insuficient funds" to either write into a log file or returning an output to the program calling this class/function.
Related
I have a Python application for which I have a logger.
At different steps of the execution, the application must read various input files. Input files which can have different information but are all read through the same function.
One of the particular information I am looking at is one called computation_id and MUST be present in one of the file but can be absent from all others.
And I am interested to know what is a correct way of handling this situation. Currently I am handling it like this :
def input_reading(filename):
results = {}
[...]
try:
results['computation_id'] = read_computation_id()
except KeyError:
pass
[...]
return results
so if the computation_id is absent from the file being read, the code shall keep running. However, at some point, I will need this computation id and therefore I need to check if it was correctly read from the file where I expect to find it.
I actually need this value far down the code. But running the code up to this point (which takes some time) to then fail is wasted computation time. So my idea is to check for this value as soon as I can and handle the error the following way :
def specifc_file_read(filename):
[...]
results = input_reading('my_file')
try:
results['computation_id']
except KeyError:
logger.exception('no computation id provided, aborting')
raise SystemExit('no computation id provided, aborting')
[...]
Is this good practice ?
I have a feeling it's not since I need to write special code lines to check for the error "as soon as I can" in the code to avoid wasting computation time.
Since I don't have much experience with error handling, I want to know if this is good practice or not in order not to keep bad habits.
It comes down to what you prefer, imo.
I think that would be more readable
if 'computation_id' not in results: raise ...
although if you want to check if it's available in any file before you begin some heavy data processing, you could to
for f in get_files():
if 'computation_id' in f:
break
else:
raise SystemExit
So it will raise SystemExit if it didn't break, so if it's not available in at least one file.
I have a script that is operating a physical device. There is a physical malfunction on the device that occurs sometimes and when it does, I want to reset the device and continue with the script. I have this:
while True:
do_device_control()
device_status = get_device_status()
if device_status == 'Fault':
reset_device()
It seems to me that a neater / more Pythonic approach would raise an Exception:
try:
while True:
do_device_control()
device_status = get_device_status()
if device_status == 'Fault':
raise DeviceFaultException()
except DeviceFaultException:
reset_device()
But as far as I can see, there is no way to resume the script after resetting the device. Is there a way to make Exception handling work for this situation, or a more Pythonic approach than what I'm currently using?
A common Python idiom is "Ask forgiveness rather than permission", which applies very well to your question. An implementation like the following would suffice:
while True:
try:
do_device_control()
except DeviceFaultException:
reset_device()
This should get similar behavior as to what is in your original block of code using if statements.
However, you probably noticed that I did not check the device_status in my code. This is because you should allow the do_device_control function to raise the exception if it is unable to complete its task. This allows you to handle exceptions at the time the actions are being executed.
In an environment where you are working with a device that is running asynchronously to your code, you may check the device status and it be fine. It then might fail between the check and your do_device_control function. This is one example of why the "ask forgiveness rather than permission" paradigm exists.
Of course, this example only works if you can expect the do_device_control function to throw some sort of exception or change it so that it does. If you cannot expect/change this behavior, your first code block with the if statement would be preferred over explicitly raising an exception just to immediately catch it within the same code block.
I try to understand what assert it is. Every answer say it is debugging stuff. I confused. Should not we use it in production? Should we remove it after the developlement? If assertions are disabled in the Python interpreter. Asserts will be ignored and that can become a security risk? We just use it try to make easy debug on development?
if it is for production. below example i used assert to control not having negative value. Why don't i use if else instead assert?
Thank you in advance.
def get_age(age):
assert age > 0, "Age can't be negative!"
print("your age is"+age)
If nothing else, you lose control over the behavior of get_age using assertions, because they can be disabled at runtime without modifying the code itself. For production usage, be more explicit and raise a ValueError (or, if you deem it worth the effort, a custom subclass of ValueError):
def get_age(age):
if age <= 0:
raise ValueError("Age can't be nonpositive!")
print("your age is" + str(age))
Assert Statements are used when:
the final code is in theory fail safe
the current state of the code is not yet fail safe
E.g. You programm a game with a moving car. Let's say you have a really complicated move() function that works absolutely fine for positive input but in special cases bugs out in weird ways for negative input.
You know this function is going to be fine in the final stage of your game as no function part of the game will every call move() with negative input.
But as you are currently still working on an A.I. driving the car so that you can race against bots - you cannot gurantee that you didn't make a mistake and the A.I. calls the move() function with negative input.
Therefore you put an assert input_arg >= 0 at the beginning of your move() function so that you get notified everytime a wrong input arg was given.
try-except will not catch this as your movement only bugs out but does not raise an exception.
So the assert input_arg >= 0, 'input_arg must be positive!' is just shorthand for if not (input_arg >= 0): raise ValueError('input_arg must be positive!') while signaling that this is not an actual error that can occur in the final stage of the code but only while developing other parts of the game.
It is shorter, can be differentiated from 'real' errors and therefore also automatically stripped away for production code.
Currently using the wxPython framework and my code looks like this:
Event bind:
self.frequency_grid.Bind(wx.grid.EVT_GRID_CELL_CHANGE, self.on_cell_changed)
Function that handles changed cells:
def on_cell_changed(self, event):
self.current_grid = event.GetEventObject()
try:
new_value= self.get_cell_value()
if new_value < 0:
raise AttributeError
#allow the cell to update
except AttributeError:
event.Veto()
wx.MessageBox(_("Positive values only."), "", wx.OK|wx.ICON_WARNING)
except:
wx.MessageBox(_("Invalid value for cell."), "", wx.OK|wx.ICON_WARNING)
event.Veto()
The function get_cell_value() reads the value from the current cell and converts it to an integer simply by using int(). If the user enters a character like 'a' into the cell, obviously this function fails and the exception is raised. In this case the messagebox comes out telling the user the cell has an invalid value. This is what I call the automatically caused exception, and the final exception block is executed.
In the case of negative values, I manually raise an AttributeError (just wanted to see something different from ValueError which is what happens when user inputs characters).
In this case however, the wxPython sends the EVT_GRID_CELL_CHANGE event twice, so there must be something different about manually raised exceptions.
I've separately raised a ticket about the duplicated events at http://trac.wxwidgets.org/ticket/16333 but just trying to understand how the first scenario doesn't make wxPython send 2 events compared to the second scenario.
Don't use except: if you really want to catch any possible error(even system memory or anything else non-related).
To make your code look better, I'd suggest to cast value to int with try...except block, then check for negative values.
try:
value = int(text)
except:
return MessageBox('Enter digit')
if value < 0:
return MessageBox('Enter positive digit')
There is no difference in the Exceptions, but you are processing them in a different way, at a different place in the code.
I suspect it's the veto() call that causes the extra event. But why not validate the input in one place only? i.e have your get_cell_value() function should make sure it's an integer >0.
Also, this structure is both a recipe for circular events, and a painfully annoying UI. People should be able to make a typo and go back and correct it without an annoying dialog popping up.
Maybe provide an indicator of a problem, like making the cell background red, but only go the dialog route once the user tries to move on to the next step.
After a bit more tracing, I found that creating the MessageBox causes the EVT_GRID_CELL_CHANGING event to occur, which then leads to the EVT_GRID_CELL_CHANGED event to occur, which is why I saw duplicated events.
The reason why I did not see duplicated events during the entry of a character was because a Veto() was called in the event handler for the EVT_GRID_CELL_CHANGING if an int() conversion was invalid because my handler for that event gets the grid input and tries to convert it.
In conclusion, there is no difference in Python exception handling, but however, a better wxPython demo should be implemented to prevent the duplicated message box during the demo and show other users how to better use the grid mechanism.
I've read three beginner-level Python books, however, I still don't understand exceptions.
Could someone give me a high level explanation?
I guess I understand that exceptions are errors in code or process that cause the code to stop working.
In the old days, when people wrote in assembly language or C, every time you called a function that might fail, you had to check whether it succeeded. So you'd have code like this:
def countlines(path):
f = open(path, 'r')
if not f:
print("Couldn't open", path)
return None
total = 0
for line in f:
value, success = int(line)
if not success:
print(line, "is not an integer")
f.close()
return None
total += value
f.close()
return total
The idea behind exceptions is that you don't worry about those exceptional cases, you just write this:
def countlines(path):
total = 0
with open(path, 'r') as f:
for line in f:
total += int(line)
return total
If Python can't open the file, or turn the line into an integer, it will raise an exception, which will automatically close the file, exit your function, and quit your whole program, printing out useful debugging information.
In some cases, you want to handle an exception instead of letting it quit your program. For example, maybe you want to print the error message and then ask the user for a different filename:
while True:
path = input("Give me a path")
try:
print(countlines(path))
break
except Exception as e:
print("That one didn't work:", e)
Once you know the basic idea that exceptions are trying to accomplish, the tutorial has a lot of useful information.
If you want more background, Wikipedia can help (although the article isn't very useful until you understand the basic idea).
If you still don't understand, ask a more specific question.
The best place to start with that is Python's list of built-in exceptions, since most you'll see derive from that.
Keep in mind that anybody can throw any error they want over anything, and then catch it and dismiss it as well. Here's one quick snippet that uses exceptions for handling instead of if/else where __get_site_file() throws an exception if the file isn't found in any of a list of paths. Despite that particular exception, the code will still work. However, the code would throw an uncaught error that stops execution if the file exists but the permissions don't allow reading.
def __setup_site_conf(self):
# Look for a site.conf in the site folder
try:
path = self.__get_site_file('site.conf')
self.__site_conf = open(path).read()
except EnvironmentError:
self.__site_conf = self.__get_site_conf_from_template()
Python's documentation: http://docs.python.org/2/tutorial/errors.html
For a high-level explanation, say we want to divide varA / varB. We know that varB can't equal 0, but we might not want to perform the check every time we do the division:
if varB != 0:
varA / varB
We can use exceptions to try the block without performing the conditional first, and then handle the behavior of the program based on whether or not something went wrong in the try block. In the following code, if varB == 0, then 'oops' is printed to the console:
try:
varA / varB
except ZeroDivisionError:
print 'oops'
Here is a list of exceptions that can be used: http://docs.python.org/2/library/exceptions.html#exceptions.BaseException
However, if you know how it may fail, you can just open a python console and see what exception is raised:
>>> 1 / 0
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: integer division or modulo by zero
Exceptions are unexpected events that occur during the execution of a program. An exception might result from a logical error or an unanticipated situation.
In Python, exceptions (also known as errors) are objects that are raised (or thrown) by code that encounters an unexpected circumstance.
The Python interpreter can also raise an exception should it encounter an unexpected condition, like running out of memory. A raised error may be caught by a surrounding context that “handles” the exception in an appropriate fashion.
If uncaught, an exception causes the interpreter to stop executing the program and to report an appropriate message to the console.
def sqrt(x):
if not isinstance(x, (int, float)):
raise TypeError( x must be numeric )
elif x < 0:
raise ValueError( x cannot be negative )
Exceptions are not necessarily errors. They are things that get raised when the code encounters something it doesn't (immediately) know how to deal with. This may be entirely acceptable, depending on how you make your code. For instance, let's say you ask a user to put in a number. You then try to take that text (string) and convert it to a number (int). If the user put in, let's say, "cat", however, this will raise an exception. You could have your code handle that exception, however, and rather than break, just give the user a small message asking him to try again, and please use a number. Look at this link to see what I'm talking about: http://www.tutorialspoint.com/python/python_exceptions.htm
Also, you usually handle exceptions with a try, except (or catch) block. Example:
try:
integer = int(raw_input("Please enter an integer: "))
except Exception, exc:
print "An error has occured."
print str(exc)
Hope it helps!