Dynamic Semantic errors in Python

2019-04-02 08:35发布

问题:

i came across this as an interview question. This question seemed interesting. So, i am posting it here.

Consider the operation which gives semantic error like division by zero. By default, python compiler gives output like "Invalid Operation" or something. Can we control the output that is given out by Python compiler, like print some other error message, skip that division by zero operation, and carry on with rest of the instructions?
And also, how can i evaluate the cost of run-time semantic checks? There are many python experts here. I am hoping someone will throw some light on this. Thanks in advance.

回答1:

Can we control the output that is given out by Python compiler, like print some other error message, skip that division by zero operation, and carry on with rest of the instructions?

No, you cannot. You can manually wrap every dangerous command with a try...except block, but I'm assuming you're talking about an automatic recovery to specific lines within a try...except block, or even completely automatically.

By the time the error has fallen through such that sys.excepthook is called, or whatever outer scope if you catch it early, the inner scopes are gone. You can change line numbers with sys.settrace in CPython although that is only an implementation detail, but since the outer scopes are gone there is no reliable recorvery mechanism.

If you try to use the humorous goto April fools module (that uses the method I just described) to jump blocks even within a file:

from goto import goto, label

try:
    1 / 0
    label .foo
    print("recovered")

except:
    goto .foo

you get an error:

Traceback (most recent call last):
  File "rcv.py", line 9, in <module>
    goto .foo
  File "rcv.py", line 9, in <module>
    goto .foo
  File "/home/joshua/src/goto-1.0/goto.py", line 272, in _trace
    frame.f_lineno = targetLine
ValueError: can't jump into the middle of a block

so I'm pretty certain it's impossible.


And also, how can i evaluate the cost of run-time semantic checks?

I don't know what that is, but you're probably looking for a line_profiler:

import random

from line_profiler import LineProfiler
profiler = LineProfiler()

def profile(function):
    profiler.add_function(function)
    return function


@profile
def foo(a, b, c):
    if not isinstance(a, int):
        raise TypeError("Is this what you mean by a 'run-time semantic check'?")

    d = b * c
    d /= a

    return d**a

profiler.enable()
for _ in range(10000):
    try:
        foo(random.choice([2, 4, 2, 5, 2, 3, "dsd"]), 4, 2)
    except TypeError:
        pass

profiler.print_stats()

output:

Timer unit: 1e-06 s

File: rcv.py
Function: foo at line 11
Total time: 0.095197 s

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
    11                                           @profile
    12                                           def foo(a, b, c):
    13     10000        29767      3.0     31.3      if not isinstance(a, int):
    14      1361         4891      3.6      5.1          raise TypeError("Is this what you mean by a 'run-time semantic check'?")
    15                                           
    16      8639        20192      2.3     21.2      d = b * c
    17      8639        20351      2.4     21.4      d /= a
    18                                           
    19      8639        19996      2.3     21.0      return d**a

So the "run-time semantic check", in this case would be taking 36.4% of the time of running foo.


If you want to time specific blocks manually that are larger than you'd use timeit on but smaller than you'd want for a profiler, instead of using two time.time() calls (which is quite an inaccurate method) I suggest Steven D'Aprano's Stopwatch context manager.



回答2:

I would just use an exception, this example is using python 3. For Python 2, simple remove the annotations after the function parameters. So you function signature would look like this -> f(a,b):

def f(a: int, b: int):
    """

    @param a:
    @param b:
    """
    try:
        c = a / b
        print(c)
    except ZeroDivisionError:
        print("You idiot, you can't do that ! :P")

if __name__ == '__main__':
    f(1, 0)

>>> from cheese import f
>>> f(0, 0)
You idiot, you can't do that ! :P
>>> f(0, 1)
0.0
>>> f(1, 0)
You idiot, you can't do that ! :P
>>> f(1, 1)
1.0

This is an example of how you could catch Zero Division, by making an exception case using ZeroDivisionError.

I won't go into any specific tools for making loggers, but you can indeed understand the costs associated with this kind of checking. You can put a start = time.time() at the start of the function and end = time.time() at the end. If you take the difference, you will get the execution time in seconds.

I hope that helps.