Suppose I have a multiline in Python which raises an Exception.
How does Python decide which line to raise the exception against?
Examples: (Note: I could have used backslashes \
after each line)
(1
+0/0
+3)
Thows an exception on line 3 (a ZeroDivisionError
exception, at +3)
).
(1
+
0/0
)
Throws an exception on line 3.
(0/0
+
1)
Throws an exception on line 2.
This question was inspired by this example, and @Godman pointed out that exceptions don't just occur on the last line (as I had previously thought).
An Exception is an error that happens during the execution of a program. Whenever there is an error, Python generates an exception that could be handled. It basically prevents the program from getting crashed.
Fundamentally, I dont think we all are thinking on the right lines. There is no such thing as the last line here. The exception is raised by the interpreter when it receives an expression fully. According to the Python grammar: http://docs.python.org/reference/grammar.html, the expression is not fully complete until you hit a closing brace ')'. A brief explanation for the same was given by Joran Beasley in the comments against the question itself.
You can do 3 things do judge the correctness of this, without delving much deep into the grammar:-
Write this code in the python interpreter:
a=(1+2+0/0+4+5)
This also raises ZeroDivionError.
Write this code in the python interpreter:
a=(1+2+0/0+4+5 # And, press enter
This gives you invalid syntax since the expression is not complete and can not be parsed by the interpreter PS: This is the same as the code mentioned in the question
a = (1
+2
+0/0
+4
+5)
Eventually, the expression does not complete until you hit the closing brace. Hence, you can continue to add on more sub-expressions inside it without getting any exception. Thus, fundamentally, the interpreter does not see this all as line numbers; it waits until the all the expressions (including the sub-expressions) have completed. And, it is a proper programming control flow for the interpeter.
PS: Forgive my formatting of the answer.
New edit:-
@ Hayden: I thought it would be easy to explain the subtleties by not delving too deep into the grammar. But, for your reference, I am just copying the code from the URL: http://nedbatchelder.com/blog/200804/the_structure_of_pyc_files.html
Steps to run:- 1. Write your code asked in the question in a temp.py file and save it, then, import temp in another file or in the interpreter. This will create temp.pyc 2. Now, copy and paste the full code in the above mentioned URL in byteCodeDetails.py and run the file in command prompt as: python byteCodeDetails.py temp.pyc. The function show_file will be called here and will give the following output:-
magic 03f30d0a
moddate 458c2e50 (Fri Aug 17 23:54:05 2012) code
argcount 0
nlocals 0 stacksize 3 flags 0040 code
640600640200640200151764030017640400175a000064050053 5
0
LOAD_CONST 6 (3)
3 LOAD_CONST 2 (0)
6 LOAD_CONST 2 (0)
9 BINARY_DIVIDE
10 BINARY_ADD
11 LOAD_CONST 3 (4)
14 BINARY_ADD
15 LOAD_CONST 4 (5)
18 BINARY_ADD
19 STORE_NAME 0 (a)
22 LOAD_CONST 5 (None)
25 RETURN_VALUE
consts
1
2
0
4
5
None
3
names ('a',)
varnames ()
freevars ()
cellvars ()
filename 'C:\Users\Python\temp1.py'
name ''
firstlineno 5
lnotab
So, as you can notice that:-
Now, just make a slight change in your initial code in the temp.py file:-
a = (1
+2
+0/0
+4+
5)
Now, Run through the above 2 steps once again. The following is the output:-
magic 03f30d0a
moddate 0f8e2e50 (Sat Aug 18 00:01:43 2012)
code
argcount 0
nlocals 0
stacksize 3
flags 0040
code 640600640200640200151764030017640400175a000064050053
4
0 LOAD_CONST 6 (3)
3 LOAD_CONST 2 (0)
6 LOAD_CONST 2 (0)
9 BINARY_DIVIDE
10 BINARY_ADD
11 LOAD_CONST 3 (4)
14 BINARY_ADD5 15 LOAD_CONST 4 (5)
18 BINARY_ADD
19 STORE_NAME 0 (a)
22 LOAD_CONST 5 (None)
25 RETURN_VALUE
consts
1
2
0
4
5
None
3
names ('a',)
varnames ()
freevars ()
cellvars ()
filename 'C:\Users\Python\temp1.py'
name ''
firstlineno 4
lnotab 0f01
Well, now you can clearly see 2 things:-
The whole point of this lengthy discussion is that wherever the byte code indicates to the interpreter that this is where a line starts and the corresponding statements which should be executed for this line, then, the interpreter just runs that line and the corresponding statements written next to it.
The code in your question shows firstlineno as 5, that is why you receive an error in line 5. Hopefully, this helps now.
The exception will point to the line* containing either:
The last operator (if the previous literals/operators caused the exception).
The last literal (otherwise i.e. the last literal/operator caused the exception).
.
However, if this is not the behaviour that you see, it may be caused by discrepancies in one of your py (source) files and either its corresponding (compiled) pyc file, or the running code (in memory). The following is an illustrative example.
Suppose E.py
contains:
def z():
0/0
From the python command line, import E
(this will compile E.py
into byte-code: E.pyc
, and puts into memory).
Call E.z()
, which will produce an exception, on line 2 in z
, displaying the line 0/0
- no surprise here.
Go back to the E.py
source file, insert two lines at the top, and on the second insert the string "oh dear, oh dear"
.
Go back in the python command line, and call E.z()
a second time.
The exception (on line 2, in z) now displays "oh dear, oh dear"
.
*Update: I don't have a reference for this, please comment one if you come across one. I had previously thought that it was the simply the last line!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With