What surprised me when we discussed the dynamic catch exception was that I could find hidden bugs and fun ...
Code with the problem
The following code comes from a product that looks like a good abstract code-slightly (!). This is a function that calls some statistics and then handles it. The first is to get a value with the socket connection, and a socket error may have occurred. Since statistics are not critical in the system, we just remember the log errors and continue to go down.
(Please note that this article I used doctest test-This means the code can run!)
>>> def get_stats ():
... pass
...
>>> def do_something_with_stats (stats):
... pass
...
>>> Try:
... stats = get_stats ()
... except Socket.error:
... logging.warning ("Can ' t get statistics")
.. else:
... do_something_with_stats (stats)
Find
We didn't find it wrong when we tested it, but we actually noticed that the static analysis report showed a problem:
$ Flake8 filename.py
Filename.py:351:1: F821 undefined name ' socket '
Filename.py:352:1: F821 undefined name ' logging '
Obviously we did not test, this problem is the code we do not reference the socket and logging two modules. To my surprise, this did not throw a nameerror error beforehand, and I thought it would look for some nouns in these exception statements, such as what it needs to know to catch these exceptions. !
It turns out that this is not the case, the lookup of the exception statement is deferred and only throws an exception when evaluated. Not only is the name deferred lookup, you can also customize the display declaration exception as ' parameter (argument) '.
This may be a good thing, a bad thing, or an abomination.
Good news (mentioned in the previous paragraph)
The exception parameters can be passed in any form of numeric value. This allows the dynamic parameters of the exception to be captured.
>>> def do_something ():
... blob
...
>>> def attempt (action, Ignore_spec):
... try:
... action ()
... except Ignore_spec:
... pass
...
>>> attempt (do_something, ignore_spec= (Nameerror, TypeError))
>>> attempt (do_something, Ignore_spec=typeerror)
Traceback (most recent):
...
Nameerror:global name ' blob ' is not defined
Bad things (mentioned in the previous paragraph)
The obvious disadvantage is that the errors in the exception parameters are usually only noticed after the exception is triggered, but it is too late. When an exception is used to catch an uncommon event (for example, to open a file in writing), unless a specific test case is made, only one exception (or any exception) When triggered, it is only known when it is recorded and checked for a matching exception, and throws its own error exception-This is what a nameerror usually does.
>>> def do_something ():
... return 1, 2
...
>>> Try:
... a, B = do_something ()
... except Valuerror: # oops-someone can ' t type
... print ("Oops")
.. else:
... print ("ok!") # We Are ' OK ' until do_something returns a triple ...
Ok!
Annoying (mentioned in the previous paragraph)
>>> Try:
... TypeError = zerodivisionerror # Now what would we do this...?!
... 1/0
... except TypeError:
... print ("caught!")
.. else:
... print ("OK")
...
caught!
Not only are exception parameters searched by name, but other expressions work as well:
>>> Try:
... 1/0
... except eval ('. Join (' Zero Division Error '. Split ()):
... print ("caught!")
.. else:
... print ("OK")
...
caught!
The exception parameter is not only determined at run time, it can even use information about the exception during the life cycle. The following is a tricky way to catch thrown exceptions-but only this:
>>> Import Sys
>>> def current_exc_type ():
... return sys.exc_info () [0]
...
>>> Try:
... blob
... except Current_exc_type ():
.. print ("Got you!")
...
Got you!
Obviously that's what we're really looking for. When we write exception handlers, the first thing we should think about is this
(byte) code
In order to confirm how it occurred in the exception handling work, I ran Dis.dis () in an exception example. (Note that the decomposition here is under Python2.7-different bytecode is generated under Python 3.3, but this is basically similar):
>>> Import Dis
>>> def x ():
... try:
... pass
... except blobbity:
... print ("bad")
.. else:
.. print ("good")
...
>>> Dis.dis (x) # doctest: +normalize_whitespace
2 0 setup_except 4 (to 7)
3 3 Pop_block
4 Jump_forward (to 29)
4 >> 7 Dup_top
8 Load_global 0 (blobbity)
Compare_op (Exception match)
28 Pop_jump_if_false
Pop_top
Pop_top
Pop_top
5 Load_const 1 (' bad ')
Print_item
Print_newline
Jump_forward 6 (to 34)
>> end_finally
7 >> load_const 2 (' good ')
Print_item
Print_newline
>> load_const 0 (None)
Panax Notoginseng Return_value
This shows the problem I originally expected (issue). The exception handling "looks" is run entirely in accordance with Python's internal mechanism. This step is completely unnecessary to know about the subsequent exception "catch" statements, and if no exceptions are thrown they will be completely ignored. Setup_except doesn't care what happens, just if an exception occurs, the first handler should be evaluated, then the second one, and so on.
Each handler has two parts: a rule that gets an exception, and a comparison with the exception that was just thrown. Everything is delayed, and everything looks just like the expectations of your progressive code, from the perspective of the interpreter. No task The clever thing happened, just suddenly made it look very smart.
Summarize
While this dynamic anomaly has surprised me, it contains a lot of interesting applications. Of course to achieve many of them may be a bad idea, hehe
Sometimes it is not always intuitive to confirm how many Python features are supported-for example, expressions and declarations are explicitly accepted in the scope of a class (rather than functions, methods, global scopes), but not all are so flexible. Although (I think) that would be nice, the expression is forbidden to apply to adorners-The following is a Python syntax error:
@ (Lambda Fn:fn)
def x ():
Pass
This is an example of trying to pass a dynamic exception parameter to the first exception by a given type, silently tolerating a duplicate exception:
>>> class Pushover (object):
... exc_spec = set ()
...
... def attempt (self, Action):
... try:
... return action ()
... except tuple (SELF.EXC_SPEC):
... pass
... except baseexception as E:
... self.exc_spec.add (e.__class__)
... raise
...
>>> pushover = pushover ()
>>>
>>> for _ in range (4):
... try:
... pushover.attempt (lambda:1/0)
... except:
... print ("Boo")
.. else:
... print ("yay!")
Boo
yay!
yay!
yay!