Python on error resume next

Previous posts I’m Nicholas FitzRoy-Dale, a software developer from London, UK. I’m interested in embedded systems and operating system research, code optimisation, 8-bit music, rock climbing, shiny things. Summary I wrote ON ERROR RESUME NEXT for Python as a proof of concept. Code is here. Intro On the whole, BASIC is an unfairly maligned […]

Содержание

  1. Previous posts
  2. Summary
  3. Intro
  4. Just to be totally clear because humour is difficult on the Internet
  5. How it ought to work
  6. Diversion: catching top-level exceptions
  7. How it actually works
  8. Getting the stack information even if it kills us
  9. Finding the number of items on the value stack
  10. Retrieving the items on the value stack
  11. Function necromancy
  12. Wait a minute, what about the block stack
  13. Handling multiple exceptions
  14. Code please
  15. Examples
  16. Recapping
  17. Does it work?
  18. Related work

Previous posts

I’m Nicholas FitzRoy-Dale, a software developer from London, UK. I’m interested in embedded systems and operating system research, code optimisation, 8-bit music, rock climbing, shiny things.

Summary

I wrote ON ERROR RESUME NEXT for Python as a proof of concept. Code is here.

Intro

On the whole, BASIC is an unfairly maligned programming language. By the 90s it had come a long way from the GOTO-peppered, line-numbered morass which earned Dijkstra’s ire. Modern BASICs have great support for structured programming — not just procedures and while loops, but classes and modules, too.В However, there is one famous BASIC feature which is every bit as evil as it sounds.

Errors in early BASICs tended to be fatal. This meant that writing robust programs meant doing a lot of careful checking before any potentially-dangerous operation. (Writing non-robust programs meant getting used to restarting them a lot). In many ways, the error handling environment of BASIC was more difficult to work with than other languages of the time, such as C, with the result that many people wished instead that they could just make errors go away so they didn’t have to think about them. Enter ON ERROR RESUME NEXT.

When a BASIC interpreter encounters ON ERROR RESUME NEXT, it stops reporting errors to the program. That’s it. Oh sure, they still happen, but you don’t need to worry about them now. When an error occurs BASIC will just continue on the next line of the program. Open a file which doesn’t exist? No worries. Divide by zero? Sure. Every path is the happy path now. (1)

So anyway, I’ve been deep in Python internals recently, and one night while drinking heavily (2) the thought occurred to me: could we implement ON ERROR RESUME NEXT in Python? Could we just have the interpreter swallow exceptions and keep going? Turns out we pretty much can!

Just to be totally clear because humour is difficult on the Internet

Yes, I did actually write this. No, I don’t recommend anyone use it. But the implementation and revealed information about how Python works is pretty interesting.

How it ought to work

Python uses the “termination” model of error handling: an exception handler can find out what happened and continue execution at an outer level, but it cannot repair the cause of the error and retry the failing operation.

When something goes wrong in Python, it raises an exception. If the exception isn’t handled by the function which generated it, it propagates back up the call stack until it reaches an exception handler. If the exception handler can’t handle that type of exception, it continues its journey upwards. If there are no suitable exception handlers, the exception propagates all the way up to the top level, and the Python interpreter itself handles it by printing a traceback and exiting. Very BASIC-like, that part.

The interesting thing about Python is that its tracebacks are very rich. As well as the line number of the failing instruction, they also contain one frame object per traceback level. Frame objects are the magic on which the present abomination turns, because frame objects contain a pointer to the compiled code that was running, the exact Python opcode that failed, the locals and globals which were active in that frame… in fact, almost everything one might need to re-start execution.

If the above has given you a terrible idea, then we should be friends. What it means is that if function1В calls function2()В and function2В throws an exception, the traceback will contain frame objects for function1В and function2. Both functions’ local variables, instruction pointer, and so on are all faithfully preserved in their respective frame objects, so if we were to somehow re-enter function2, restoring all its state, and jump directly to the the line of code after the one which threw the exception, we could effectively RESUME NEXT.В

Basically, then, we are going to

  1. Take the bytecode from the frame object and add a portion at the beginning which
  2. Restores the function context we pulled out of the frame stack and
  3. Jumps directly to either the instruction which performs the function call to the next-lowest function in the traceback  (if this wasn’t the frame which produced the exception), or the instruction corresponding to the next line of code (if this was the frame that produced the exception). Then we
  4. Take the top-level patched function, the one at the outermost level of the traceback, and run it.

The top-level function will call all the way down the call В hierarchy pulled out of the traceback, finally reaching the function which raised the exception. That final, leaf function will continue on the line after the one which raised. Simple! ON ERROR RESUME NEXT.

Diversion: catching top-level exceptions

You might be thinking (I was), “Nicholas, this isn’t very BASIC if I have to wrap all our code in an exception handler. That’s exactly the sort of error shenanigans I was hoping to avoid!” Well, fortunately, Python offers sys.excepthook as a last-resort way of handling top-level exceptions. If sys.excepthook is set, exceptions which reach the top level are passed to it. So we can implement our RESUME NEXT from there. More on this later. For now, let’s dive into the horrifying world of Python’s four different stacks.

How it actually works

There are lots of implementations of Python, but unless you’re doing something quite specialised you’ll be using the one available from python.org known as CPython. CPython bytecode is stack-based. In fact, Python’s creators loved stacks so much, they used four of them. They are:

  1. The Python call stack. This is an array of frame objects, one per function invocation. When you call a function, Python creates a new frame object and puts it on this stack. When the function returns, Python pops its frame object off the call stack. Simple.
  2. The C stack. CPython is written in C, hence the name, and its interpreter uses the C stack internally in the way all C programs do, for storing call frames and local variables.
  3. The block stack. When you write an exception handler, Python puts the location of its finally clause (which may be empty if you didn’t write one) on the block stack. It’s also used for with, and a few other things. Each frame has its own block stack, and when an exception is raised this is what’s consulted to find a handler.
  4. The value stack. In a stack-based language like CPython, all data is passed on the stack. A line like value = 42В is translated to bytecode which first pushes 42 onto the stack, and then pops the top of the stack and stores the result in value. Each frame has its own value stack, too.

I wrote above that tracebacks contain “almost everything” required to re-run the code causing an exception. Guess what tracebacks don’t contain? That’s right, the stacks!

(Except the Python call stack, of course. We get that one.)

Getting the stack information even if it kills us

The real killer is the value stack. Because Python uses the value stack for everything, it’s really important that we restore it. As an example, consider this code snippet:

This disassembles to the following bytecode:

Let’s say that explodey throws an exception, resulting in CALL_FUNCTION failing. We want to resume at the next instruction (3) here, which is STORE_FAST. So we will just jump right back into the function at STORE_FAST, index 6, and… immediately hit a stack underflow error because STORE_FAST is expecting to pop something (the thing it’s storing fast!) off the value stack.

Okay, no problem. The value stack is there, in the frame object. It’s just not obviously accessible to us. Once we have the value stack and the value stack pointer, which tells us how many value stack objects are active, we can pull that number of objects out of the frame and push them back onto the stack when we call the function again to retry. Sounds… simple?

Finding the number of items on the value stack

You can’t. Python doesn’t expose this information in any way, and in fact it couldn’t even if it wanted to because the stack pointer is a local variable stored on the interpreter’s (C) stack, and by the time we’ve got the traceback those (C) stack frames have disappeared.

Alright, so how do we get this information if it’s literally not there? Well, it is there. In a way.

Look again at the disassembly above. We see LOAD_GLOBAL, LOAD_FAST, and so on. Looking at the documentation for these opcode we see that both of these push a value onto the stack. CALL_FUNCTION (1) consumes the number of arguments supplied (1), plus the callable itself, but it then pushes the function’s return value. STORE_FAST pops a value. And so on.

For this function, execution stopped at CALL_FUNCTION. That means we executed LOAD_GLOBALВ (add 1 to the stack pointer), LOAD_FASTВ (add 1 again), and CALL_FUNCTIONВ (subtract 1 for the argument, subtract another 1 for the callable, then add 1 for the return code). So just by looking at the code, we see that by the time CALL_FUNCTIONВ has finished, we have one value on the stack.

Can we formalise this? Of course we can, in the form of an abstract interpreter. We “execute” the Python bytecode, but the only thing we keep track of is the current stack depth. When we reach the instruction of interest (i.e. the one which produced the exception, i.e. CALL_FUNCTION in this example), we stop.

Abstract interpretation sounds hard, but it’s actually easier than you might expect. For example, the stack level before a loop is the same as the stack level after a loop, no matter how many times the loop executes. So we don’t need to worry about how many times a loop will execute. No matter what a function does, CALL_FUNCTION will always push exactly one value. So we don’t need to trace our way through function calls. We do need to worry about opcodes like JUMP_ABSOLUTE, which change the program counter, but that’s relatively straightforward. We also need to worry about conditional jumps — but we can handle those by trying both options (jump taken and jump not taken), one after the other. Our abstract interpreter just wombles through the code, not worrying too much about what it does but maintaining a stack pointer, until its instruction pointer matches the one we’re interested in. Then we stop interpreting and return the stack depth at that point.

Alright! So now we know the number of items we can expect to have on the value stack! Now to retrieve them!

Retrieving the items on the value stack

You can’t. Python doesn’t expose the value stack to Python code in any way.В

Soooo it’s time to pull out the ctypesВ module and get digging. The frame object is represented in CPython by a struct _frame — and if you visit that link you can see a tantalising PyObject **f_valuestack. (There is also an f_stacktop, which would be really handy to find the number of items on the stack, if Python ever set it!). There are a couple of caveats to just gleefully pulling values out of f_valuestackВ though. The most important is that CPython uses NULLВ return values internally (and in extension modules) to signal an exception. If an opcode which writes to the stack produces an exception, the stack value at that point will be NULL, so we need to take care to replace that with something sensible, like None.В

At first it might seem like quite a limitation to have stack values nulled out like this on exceptions, but it makes sense: what is the “return value” of a function which raises an exception? Any possible answer has the same information content (i.e. none), so we don’t lose anything by having to deal with NULL.

Function necromancy

As I hinted above, we can “re-enter” an exception by patching the code for the functions at every level of the traceback so that it restores the internal state of the function to what it was prior to the exception and then resumes where it left off. To make this happen we need to patch the bytecode for each function, and because you can’t modify bytecode what that actually means is that we’ll be creating temporary proxy functions with new bytecode. Doing things this way is also safer, because we can restore different states for the same function call in different frames (think recursion). There are a few things we need to restore:

  • Function locals: The frame object contains the locals. You can’t set function locals directly, but you can pass them as parameters. So the proxy function includes one parameter for each local variable.
  • Stack restoration: We can pass the stack in as a function const (a tuple of values). Restoring it involves loading the const and unpacking it. To accomplish this we add some bytecode to the start of the function.
  • Resuming at the right instruction: we do this by adding a JUMP_ABSOLUTEВ after the stack restoration.
  • Jump offset fixups: adding code at the start of the function means that all absolute jump targets in the code are invalidated, so we fix those up (by adding the length of our inserted code to them).
  • Other fun things: patching an already-patched function would get confusing, so we also store (in the function’s consts) the original frame (containing the original code object and line numbers etc). That way if we have multiple errors in the same function, we can always work from the “original”. This also means that we need a way to detect an already-patched function, which we do through a special signature (NOP with a nonzero argument as the first opcode).

It’s also easy to forget that traceback frames don’t necessarily represent functions. In fact, the first level of a traceback is quite commonly a module object. So we can’t make function-specific assumptions (such as that locals() is unique or even is distinct from globals().

Wait a minute, what about the block stack

Well yes, about that… it’s not actually vital to get a simple proof of concept going, and, well, I guess you can see where I’m heading with this. It should be fairly straightforward to rebuild it using the same abstract interpreter that calculates stack depth, though, because all the important information (jump targets) is static.

Handling multiple exceptions

Actually, the most fiddly part of this is also a rather boring bit: we will be re-entering our call stack from the sys.excepthook exception handler, but if that exception handler itself throws an exception then Python will unceremoniously kill us off. What this would mean is that we can’t ON ERROR RESUME NEXT more than once. The solution, of course, is to wrap the code we’re resuming in a try: except: block. This adds some complexity around patching already-patched functions, which I handle by keeping a copy of the original function around. See the code for more details. And speaking of the code.

Code please

The code is at https://github.com/nfd/on_error_resume_next. A good place to start reading is at the beginning of _resume, which is called from sys.excepthook with a fresh traceback.

Because it’s reaching around inside CPython internals, it requires, specifically, CPython 3.9. (It may work on 3.8 as well, possibly with some minor changes to the abstract interpreter)

Examples

Recapping

I wrote an abstract interpreter, bytecode rewriter and patcher, and CPython frame object extractor just for a stupid joke about error handling.

On the plus side, I now know quite a bit more about how Python works internally.

Does it work?

Well, I’m certain there are loads of bugs. There are also glaring omissions (see block stack). Also it’s a terrible idea. But I put all of these things into a program running ON ERROR RESUME NEXT and the answer appears to be True.

The sensitive reader will be disquieted to learn that there is in fact an existing implementation of, effectively, ON ERROR RESUME NEXTВ in Python. It’s called fuckit.py and it works by effectively wrapping every single line of code in a try: . except Exception: passВ block. Which is kind of brutal but presumably significantly more robust than this… thing.

(1) To be fair to ON ERROR RESUME NEXT, what you’re supposedВ to do is check err() in the next instruction, which gives you C-style error handling. But it is of course super tempting to just ignore all the errors instead. Again, rather like C.

(2) I’m pretty sure I was sober, actually, but if I admit that then what’s my excuse?

(3) Next instruction? What happened to next line? Well it makes sense to resume on the next line in the function which generates the exception, but not on parent (caller) functions. What if explodey did some useful work in the lines after the exception and managed to return a useful value?В

Источник

Here are the examples of how to on error resume next in python. These are taken from open source projects. By voting up you can indicate which examples are most useful and appropriate.

    def __on_error_resume_next(self, second):
        """Continues an observable sequence that is terminated normally or by 
        an exception with the next observable sequence.
    
        Keyword arguments:
        second -- Second observable sequence used to produce results after the first sequence terminates.
     
        Returns an observable sequence that concatenates the first and second sequence, even if the first sequence terminates exceptionally.
        """
    
        if not second:
            raise Exception('Second observable is required')
        
        return Observable.on_error_resume_next([self, second])
@extensionmethod(Observable, instancemethod=True)
def on_error_resume_next(self, second):
    """Continues an observable sequence that is terminated normally or by
    an exception with the next observable sequence.

    Keyword arguments:
    second -- Second observable sequence used to produce results after the
        first sequence terminates.

    Returns an observable sequence that concatenates the first and second
    sequence, even if the first sequence terminates exceptionally.
    """

    if not second:
        raise Exception('Second observable is required')

    return Observable.on_error_resume_next([self, second])
    @classmethod
    def on_error_resume_next(cls, *args):
        """Continues an observable sequence that is terminated normally or by 
        an exception with the next observable sequence.
     
        1 - res = Observable.on_error_resume_next(xs, ys, zs)
        2 - res = Observable.on_error_resume_next([xs, ys, zs])
    
        Returns an observable sequence that concatenates the source sequences, 
        even if a sequence terminates exceptionally.   
        """
    
        if args and isinstance(args[0], list):
            sources = args[0]
        else:
            sources = list(args)

        def subscribe(observer):
            subscription = SerialDisposable()
            pos = 0
            
            def action(this, state=None):
                nonlocal pos

                if pos < len(sources):
                    current = sources[pos]
                    pos += 1
                    d = SingleAssignmentDisposable()
                    subscription.disposable = d
                    d.disposable = current.subscribe(observer.on_next, lambda ex: this(), lambda: this())
                else:
                    observer.on_completed()
                
            cancelable = immediate_scheduler.schedule_recursive(action)
            return CompositeDisposable(subscription, cancelable)
        return AnonymousObservable(subscribe)
@extensionclassmethod(Observable)
def on_error_resume_next(cls, *args):
    """Continues an observable sequence that is terminated normally or by
    an exception with the next observable sequence.

    1 - res = Observable.on_error_resume_next(xs, ys, zs)
    2 - res = Observable.on_error_resume_next([xs, ys, zs])
    3 - res = Observable.on_error_resume_next(xs, factory)

    Returns an observable sequence that concatenates the source sequences,
    even if a sequence terminates exceptionally.
    """

    scheduler = current_thread_scheduler

    if args and isinstance(args[0], list):
        sources = iter(args[0])
    else:
        sources = iter(args)

    def subscribe(observer):
        subscription = SerialDisposable()
        cancelable = SerialDisposable()

        def action(scheduler, state=None):
            try:
                source = next(sources)
            except StopIteration:
                observer.on_completed()
                return

            # Allow source to be a factory method taking an error
            source = source(state) if callable(source) else source
            current = Observable.from_future(source)

            d = SingleAssignmentDisposable()
            subscription.disposable = d

            def on_resume(state=None):
                scheduler.schedule(action, state)

            d.disposable = current.subscribe(observer.on_next, on_resume, on_resume)

        cancelable.disposable = scheduler.schedule(action)
        return CompositeDisposable(subscription, cancelable)
    return AnonymousObservable(subscribe)
import imp, ast, sys, inspect def on_error_resume_next(path): file_ = open(path) tree = ast.parse(file_.read(), path) OnErrorResumeNextVisitor().visit(tree) ast.fix_missing_locations(tree) return compile(tree, path, ‘exec’) class OnErrorResumeNextFinder(object): def _find_module(self, name, path): name = name.split(‘.’)[1] file_, file_path, desc = imp.find_module(name, path) if file_: file_.close() return file_path, desc def find_module(self, name, search_path=None): file_path, desc = self._find_module(name, search_path) if desc[2] == imp.PKG_DIRECTORY: return OnErrorResumeNextLoader(file_path+‘/__init__.py’, [file_path]) elif desc[2] == imp.PY_SOURCE: return OnErrorResumeNextLoader(file_path) else: return class OnErrorResumeNextLoader(object): def __init__(self, file_path, module_path=None): self.file_path = file_path self.module_path = module_path def _compile(self): return on_error_resume_next(self.file_path) def load_module(self, name): mod = sys.modules.setdefault(name, imp.new_module(name)) mod.__loader__ = self co = self._compile() if self.module_path: mod.__path__ = self.module_path mod.__package__ = name else: mod.__package__ = name.rpartition(‘.’)[0] mod.__file__ = self.file_path exec(co, mod.__dict__) return mod def get_code(self, name): return self._compile() def get_source(self, name): return open(self.file_path).read() def get_filename(self, name): return self.file_path class OnErrorResumeNextVisitor(ast.NodeTransformer): stmt = ( ast.FunctionDef, ast.ClassDef, ast.Return, ast.Delete, ast.Assign, ast.AugAssign, ast.Print, ast.For, ast.While, ast.If, ast.With, ast.Raise, # This seems like a bad plan ast.TryExcept, ast.TryFinally, ast.Assert, # I like this one ast.Import, # ast.ImportFrom, from __future__ import can’t be in a try ast.Exec, ast.Global, ast.Expr, ast.Pass, # I hate when pass fails ast.Break, ast.Continue ) def make_try(self, node): return ast.TryExcept([node], [ast.ExceptHandler(None, None, [ast.Pass()])], []) def visit(self, node): node = ast.NodeTransformer.visit(self, node) if isinstance(node, self.stmt): node = self.make_try(node) return node def visit_ImportFrom(self, node): if node.module != ‘__future__’: node = self.make_try(node) return node # Patch the world sys.meta_path.insert(0, OnErrorResumeNextFinder()) # If this import was from the main module rerun it stack = inspect.stack() try: if len(stack) == 2 and stack[1][0].f_locals.get(‘__name__’) == ‘__main__’: file_path = stack[1][1] mod = imp.new_module(‘__main__’) mod.__name__ = ‘__main__’ sys.modules[‘__main__’] = mod code = on_error_resume_next(file_path) eval(code, mod.__dict__) sys.exit() finally: del stack

Python Errors and Exceptions | Python 3 Exception Handling Tutorial For Beginners 2018. Here in this blog post Coding compiler sharing Python 3 Errors and Exceptions tutorial for beginners. You will learn about Python exception handling as well. This Python tutorial is for beginners and intermediate learners who are looking to master in Python programming. Experienced Python programmers also can refer this tutorial to brush-up their Python 3 programming skills. Let’s start learning Python 3.

There has been no further discussion of error messages so far, but you may have already encountered some of the examples you have experimented with. There are (at least) two kinds of errors in Python: syntax errors and exceptions ( syntax errors and exceptions ).

Python Errors and Exceptions Tutorial
1. Grammatical errors 5. User-defined exceptions
2. Exceptions 6. Defining Cleanup Behavior
3. Exception Handling 7. Predefined Cleanup Behavior
4. Throwing an exception 8. Python Errors and Exceptions Conclusion

Related Articles: Python Getting Started Guide  |  Introduction to Python

1. Grammatical errors

Grammatical errors, also known as parsing errors, are probably the most common complaints you have while learning Python:

>>> the while  True  Print ( 'the Hello world' ) 
  ? File "<stdin>", Line 1, in 
    the while True Print ( 'the Hello world') 
                   ^ 
SyntaxError: invalid syntax

The parser indicates the wrong line and displays a small “arrow” in front of where the error was detected. Error is indicated by the arrow in front of (or at least so detected) caused marked: in this example, the function print () was found to errors, because the less a colon in front of it ( ':'). The error will output the file name and line number, so if you input from a script you know where to go to check for errors.

2. Exceptions

Even if a statement or expression is syntactically correct, it may throw an error when it tries to execute it. Run-time detected errors known as anomalies , and the program will not unconditional collapse: Soon, you will learn how to handle them in Python programs.

However, most of the exceptions are not processed by the program and will eventually produce an error message as shown here:

>>> 10  *  ( 1 / 0 ) 
Traceback (most recent call last): 
  File "<stdin>" , line 1 , in ? 
ZeroDivisionError : int division or modulo by zero 
>>> 4  +  spam * 3 
Traceback (most recent Call last): 
  File "<stdin>" , line 1 , in ? 
NameError : name 'spam' is not defined 
>>> '2'  +  2 
Traceback (most recent call last): 
  File "<Stdin>" , line1 , in ? 
TypeError : Can't convert 'int' object to str implicitly

The last line of the error message indicates what went wrong. Exceptions also have different types. Exception types are shown as part of the error message: The exceptions in the example are ZeroDivisionError , NameError , and TypeError . When an error message is printed, the type of the exception is displayed as an abnormal built-in name.

This is true for all built-in exceptions, but user-defined exceptions are not (although this is a very useful convention). The standard exception name is a built-in ID (no reserved keywords).

This part of the line is a detailed description of the type of exception, which means that its content depends on the type of exception.

The first half of the error message lists the location of the exception in a stack. The source code line is usually listed in the stack, however, the source code from the standard input is not displayed.

Related Article: Python Interpreter Tutorial

3. Exception Handling

It is feasible to program selected exceptions through programming. Look at the following example: It will always require user input until enter a valid integer, but allows the user to interrupt the program (using Control-Cor any method supported by the system).

Note: A user generated interrupt will raise a Keyboard Interrupt exception.

>>> while  True : 
...     try : 
...         x  =  int ( input ( "Please enter a number: " )) 
...         break 
...     except  ValueError : 
...         print ( "Oops! That was no Valid number. Try again..." ) 
...

The try statement works as follows.

  • First, execute the try clause ( the part between the try and except keywords).

  • If no exception occurs, the except clause is ignored after the try statement is executed.

  • If an exception occurs during the execution of the try clause, the rest of the clause is ignored.

    If the exception matches the type of exception specified after the except keyword, the corresponding except clause is executed. Then continue with the code after the try statement.

  • If an exception occurs , there is no matching branch in the except clause, and it is passed to the previous try statement.

    If you still can not find the corresponding final statement processing, it becomes a unhandled exception and execution stops with a message as shown.

A try statement may contain more than one except clauses that specify different handling of exceptions. At most only one branch will be executed. Exception handlers only handle exceptions that occur in the corresponding try clause.

In the same try statement, exceptions that occur in other clauses are not processed. An except clause can list multiple exception names in parentheses, for example:

...  except  ( RuntimeError ,  TypeError ,  NameError ): 
...      pass

The last except clause can omit the exception name for use as a wildcard. You need to use this method with caution because it will easily hide an actual program error!

You can use this method to print an error message and then re throw the exception (allowing the caller to handle this exception):

Import  sys

Try : 
    f  =  open ( 'myfile.txt' ) 
    s  =  f . readline () 
    i  =  int ( s . strip ()) 
except  OSError  as  err : 
    print ( "OS error: {0} " . format ( err )) 
Except  ValueError : 
    print ( "Could not convert data to an integer." ) 
except : 
    print ( "Unexpected error:" , SYS . exc_info () [ 0 ]) 
    The raise

A try … except statement can have an else clause that can only appear after all except clauses. When the try statement does not throw an exception, some code needs to be executed and this clause can be used. E.g:

for  Arg  in  SYS . the argv [ . 1 :]: 
    the try : 
        F  =  Open ( Arg ,  'R & lt' ) 
    the except  IOError : 
        Print ( 'CAN Not Open' ,  Arg ) 
    the else : 
        Print ( Arg ,  'has' ,  len ( F . the readlines ()),  'Lines' ) 
        F . Close ()

It is better to use the else clause than to append the code in the try clause, because this avoids try … except accidentally intercepting exceptions thrown by those code that are not protected by them.

When an exception occurs, it may have an associated value, as abnormal parameters exist. Whether this parameter exists or not, depends on the type of the exception.

You can also specify a variable for the except clause after the exception name (list). The variable is bound to an exception instance, it is stored in instance.argsa parameter.

For convenience, the exception instance defines __str__() so that the print parameters can be accessed directly without reference .args. This practice is not encouraged.

Related Articles: Learn Python In One Day

Instead, it is better to pass an argument to the exception (if you pass multiple arguments, you can pass a tuple) and bind it to the message attribute. Once an exception occurs, it binds all the specified attributes before throwing it.

>>> try : 
...    raise  Exception ( 'spam' ,  'eggs' ) 
... except  Exception  as  inst : 
...    print ( type ( inst ))     # the exception instance 
...    print ( inst . args )      # arguments stored in .args 
...    print ( inst )           # __str__ allows args to be printed directly, 
...                         # but may be overridden in exception subclasses 
...    x , y  =  inst . args      # unpack args 
...    print ( 'x =' ,  x ) 
...    print ( 'y =' ,  y ) 
... 
<class 'Exception'> 
('spam', 'eggs') 
('spam', 'eggs') 
x = spam 
y = eggs

For those unhandled exceptions, if one of them has parameters, it will be printed as the last part of the exception information (“details”).

Exception handlers not only handle exceptions that occur immediately in the try clause, they also handle exceptions that occur inside functions called in those try clauses. E.g:

>>> def  this_fails (): 
...     x  =  1 / 0 
... 
>>> try : 
...     this_fails () 
... except  ZeroDivisionError  as  err : 
...     print ( 'Handling run-time error: ' ,  err ) 
... 
Handling run-time error: int division or modulo by zero

Related Article: Python For Machine Learning

4. Throwing an exception

The raise statement allows the programmer to force throw a specified exception. E.g:

>>> raise  NameError ( 'HiThere' ) 
Traceback (most recent call last): 
  File "<stdin>" , line 1 , in ? 
NameError : HiThere

The exception to be thrown is identified by the raise ‘s unique parameter. It must be an exception instance or an exception class (class inherited from Exception ).

If you need to specify whether an exception was thrown, but do not want to deal with it, the raise statement allows you to simply re-throw the exception:

>>> try : 
...     raise  NameError ( 'HiThere' ) 
... except  NameError : 
...     print ( 'An exception flew by!' ) 
...     raise 
... 
An exception flew by! 
Traceback (most recent Call last): 
  File "<stdin>" , line 2 , in ? 
NameError : HiThere

5. User-defined exceptions

Programs may name their own exceptions by creating a new exception type (content type, see the Python class ). Exception classes should usually be derived directly or indirectly from the Exception class, for example:

>>> class  MyError ( Exception ): 
...     def  __init__ ( self ,  value ): 
...         self . value  =  value 
...     def  __str__ ( self ): 
...         return  repr ( self . value ) 
... 
>>> try : 
...     raise  MyError ( 2 * 2 ) 
... except  MyError  as  e :
...     print ( 'My exception occurred, value:' ,  e . value ) 
... 
My exception occurred, value: 4 
>>> raise  MyError ( 'oops!' ) 
Traceback (most recent call last): 
  File "< Stdin>" , line 1 , in ? 
__main__.MyError : 'oops!'

In this example, Exception’s default __init__() is overridden. The new way simply creates the value attribute. This replaces the original way to create args properties.

An exception class can define anything that can be defined in any other class, but generally it is only for the sake of simplicity that only a few attribute information is added to it, so that an exception handling handle can be extracted.

Related Article: Python Data Structures

If a newly created module needs to throw several different kinds of errors, a common practice is to define an exception base class for the module, and then derive the corresponding exception subclass for different types of errors:

Class  Error ( Exception ): 
    """Base class for exceptions in this module.""" 
    pass

Class  InputError ( Error ): 
    """Exception raised for errors in the input.

    Attributes: 
        expression -- input expression in which the error occurred 
        message -- explanation of the error 
    """

    Def  __init__ ( self ,  expression ,  message ): 
        self . expression  =  expression 
        self . message  =  message

Class  TransitionError ( Error ): 
    """Raised when an operations attempts a state transition that's not 
    allowed.

    Attributes: 
        previous -- state at beginning of transition 
        next -- attempted new state 
        message -- explanation of why the specific transition is not allowed 
    """

    Def  __init__ ( self ,  previous ,  next ,  message ): 
        self . previous  =  previous 
        self . next  =  next 
        self . message  =  message

Similar to standard exceptions, most exceptions are named with an “Error”.

Many of the standard modules have their own exceptions defined to report possible errors in the functions they define.

Related Article: Python Modules

6. Defining Cleanup Behavior

The try statement has another optional clause that aims to define the functions that must be performed under any circumstances. E.g:

>>> try : 
...     raise  KeyboardInterrupt 
... finally : 
...     print ( 'Goodbye, world!' ) 
... 
Goodbye, world! 
KeyboardInterrupt 
Traceback (most recent call last): 
  File "<stdin>" , Line 2 , in ?

The finally clause must be executed after the program leaves the try whether or not an exception occurs . When try occurs not statements except exception trapping (or it happens except or else clause), the finally clause is executed after it is re-thrown.

The try statement exits via the break , continue, or return statement will also execute the finally clause. The following is a more complicated example:

>>> def  divide ( x ,  y ): 
...     try : 
...         result  =  x  /  y 
...     except  ZeroDivisionError : 
...         print ( "division by zero!" ) 
...     else : 
...         Print ( "result is" ,  result ) 
...     finally : 
...         print ( "executing finally clause" ) 
... 
>>> divide ( 2 , 1 ) 
result is 2 
executing finally clause 
>>> divide ( 2 ,  0 ) 
division by zero! 
executing finally clause 
>>> divide ( "2" ,  "1" ) 
executing finally clause 
Traceback (most recent call last): 
  File " <stdin>" , line 1 , in ? 
  File "<stdin>" , line 3 , in divide 
TypeError : unsupported operand type(s) for /: 'str' and 'str'

As you can see, the finally clause executes under any circumstances. The TypeError is thrown when two strings are divided and is not caught by the except clause, so it is re-throwed after the finally clause is executed.

In a real-world application, the finally clause is used to free external resources (files or network connections, etc.) regardless of whether they are in use or not.

Related Article: Python Input and Output

7. Predefined Cleanup Behavior

Some objects define a standard cleanup behavior, which will work regardless of whether the object is successful or not. The following example attempts to open the file and print the content to the screen.

For  line  in  open ( "myfile.txt" ): 
    print ( line )

The problem with this code is that the open file is not closed immediately after the code is executed. This is nothing in simple scripts, but large applications can be problematic. The with statement makes it possible to ensure that objects such as files can always be cleaned in a timely manner.

With  open ( "myfile.txt" )  as  f : 
    for  line  in  f : 
        print ( line )

After the statement is executed, the file f will always be closed even if the error occurs when the data in the file is processed. Whether other objects provide predefined cleanup behaviors to view their documentation.

Related Articles: 

Python Interview Questions

Python Programming Interview Questions

Понравилась статья? Поделить с друзьями:
  • Syntax error inconsistent use of tabs and spaces in indentation
  • Python 504 error
  • Syntax error at or near sql что это
  • Pyinstaller error the following arguments are required scriptname
  • Table is full орион как исправить ошибку