Python Revisited Day 09 (debugging, testing and Profiling)

9.1 Debugging

Is regularly backed up to a programming key - no matter how little our machines, operating systems, and how reliable the probability of occurrence of a failure - because failure is still likely to occur. It is generally coarse-grained backup - backup file is before a few hours, or even days before.

9.1.1 grammar mistakes


if True
    print("stupid!!!")
else:
    print("You will never see me...")
  File "C:/Py/modeltest.py", line 5
    if True
          ^
SyntaxError: invalid syntax

In the above example, if the rear forget to add a ":", so the error.


try:
    s = "Tomorrow is a new day, {0}"
    s2 = "gone with the wind..."
    print(s.format(s2)

except ValueError as err:
    print(err)
  File "C:/Py/modeltest.py", line 10
    except ValueError as err:
         ^
SyntaxError: invalid syntax

See the example above, in fact, being given location and no errors, the real mistake is the print fewer side brackets, but did not realize the error in Python to run here when the brackets as possible through branches, it displays an error in the next line.

9.1.2 handle runtime errors

pass

9.1.3 debugging science

If the program can run, but the program or inconsistent behavior and expectations of need, there is a logic error bug-- must clear the program instructions. The best way to clear this type of error is the first to use TDD (test-driven development) to prevent this type of error occurs, however, there is always some bug does not avoid, therefore, even if the use of TDD, debugging still must learn and master skill.

Is clearly a bug, we must take the following step:

  1. Reproduction bug
  2. Positioning bug
  3. Bug fixes
  4. The repair of test

Pycharm Debug debugging experience - pick up & drop wrench keyboard

9.2 Unit Testing

Unit Test - test the individual functions, classes, and methods, to ensure that it complies with expected behavior.

Just as we did before:

if __name__ == "__main__":
    import doctest
    doctest.testmod()

Another method is to create a separate doctest execution of the test program using uniitest module. unittest module can create test cases based on doctests without guidance program or module contains anything - as long as it contains guidance doctest can be.

We have created a docunit.py program:



def test(x):
    """
    >>> test(-1)
    'hahahaha'
    >>> test(1)
    'lalalala'
    >>> test('1')
    'wuwuwuwuwuwu'
    """
    s1 = "hahahahha"
    s2 = "lalalalala"
    s3 = "wuwuwuwuwuwu"
    try:
        if x <= 0:
            return s1
        else:
            return s2
    except:
        return s3

Note that if you run the test, the former two strips to be wrong, because they do not match.

Then create a new program:



import doctest
import unittest
import docunit


suite = unittest.TestSuite()
suite.addTest(doctest.DocTestSuite(docunit))
runner = unittest.TextTestRunner()
print(runner.run(suite))

Note that the third import in their own program, the output is:

<unittest.runner.TextTestResult run=1 errors=0 failures=1>
F
======================================================================
FAIL: test (docunit)
Doctest: docunit.test
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\Ana\lib\doctest.py", line 2198, in runTest
    raise self.failureException(self.format_failure(new.getvalue()))
AssertionError: Failed doctest test for docunit.test
  File "C:\Py\docunit.py", line 7, in test

----------------------------------------------------------------------
File "C:\Py\docunit.py", line 9, in docunit.test
Failed example:
    test(-1)
Expected:
    'hahahaha'
Got:
    'hahahahha'
----------------------------------------------------------------------
File "C:\Py\docunit.py", line 11, in docunit.test
Failed example:
    test(1)
Expected:
    'lalalala'
Got:
    'lalalalala'


----------------------------------------------------------------------
Ran 1 test in 0.000s

FAILED (failures=1)

Process finished with exit code 0

But, this time, we write the program name of the program, you must be a valid module name.

unittest module defines four key concepts. The test fixture is a term used to describe a test to create (and exhausted after cleaning it) code is necessary, and a typical example is to create an input file used in the test, with the final result output file erasing input file. Test Suite is a set of test cases. It is the basic unit test test. Test runs is performing one or more test suite object.
Typically, the test suite is achieved by subclassing unittest.TestCase, where each method name beginning with "test" is a test case. If we need to complete any method of creation, you can be in a setUp (named) in realization; similarly, for any clean-up operation, can also be a method called tearDown () implementation. In internal testing, a large number of available unittest.TestCase method we use, including assertTrue (), assertEqual (), assertAlmostEqual () ( useful for testing floating point), assertRaises () and more, also includes corresponding inverse method such assertFalse (), assertNotEqual (), faillfEqual (), failUnlessEqual () and the like.

Here is an example, because they do not know what a compile, use one of the most simple, just to illustrate how to use this unittest.


import unittest



class List(list):

    def plus(self, other):
        return list(set(self + other))



class TestList(unittest.TestCase):

    def setUp(self):
        self.list1 = List(range(3))
        self.list2 = list(range(2, 5))

    def test_list_add(self):
        addlist = self.list1 + self.list2
        self.assertEqual(
            addlist, [0, 1, 2, 2, 3, 4]
        )

    def test_list_plus(self):
        pluslist = self.list1.plus(self.list2)
        self.assertNotEqual(
            pluslist, [0, 1, 2, 2, 3, 4]
        )
        def process():
            self.list2.plus(self.list1)
        self.assertRaises(
            AttributeError, process   #注意assertRaises的第二项必须callable Obj
        )

    def tearDown(self):
        """
        我不知道这么做有没有用
        :return: 
        """
        del self

if __name__ == "__main__":
    suite = unittest.TestLoader().loadTestsFromTestCase(
        TestList
    )
    runner = unittest.TextTestRunner()
    print(runner.run(suite))

More functions in the blog, quite detailed:
Python's unittest unit testing framework assertion finishing Summary - black-faced fox

9.3 Profiling

Some reasonable Python programming style, to improve application performance is not without benefits:

  1. Need read-only sequence is not preferable to use the list of tuples;
  2. Using generators, rather than creating large lists and tuples and iterate on it
  3. Python make use of built-in data structures --dicts, list, tuples-- not implement their own definition structure
  4. When small strings to produce a large, small string not connected, but accumulate in the list, the final list of strings into a single string binding
  5. Finally, if an object property is used many times to access, or access it from a data structure, then create and use a local variable to access the object when good practice.

In jupiter notebook inside a cell run time %% time output of a single, %% timeit output runs 100,000 times? Between the average.

Use timeit modules:

import timeit

def function_a(x, y):
    for i in range(10000):
        x + y

def function_b(x, y):
    for i in range(10000):
        x * y

def function_c(x, y):
    for i in range(10000):
        x / y



if __name__ == "__main__":
    repeats = 1000
    X = 123.123
    Y = 43.432
    for function in ("function_a", "function_b",
                     "function_c"):
        t = timeit.Timer("{0}(X, Y)".format(function),
                         "from __main__ import {0}, X, Y".format(function))
        sec = t.timeit(repeats) / repeats
        print("{function}() {sec:.6f} sec".format(**locals()))

Wherein timeit.Timer () function is the first parameter, we need to perform a string, the second parameter is an executable string, it is used to provide parameters.

function_a() 0.000386 sec
function_b() 0.000384 sec
function_c() 0.000392 sec

Using cProfile module, it will be more easily and in detail to give an indication of the running time:

import cProfile
import time


def function_a(x, y):
    for i in range(10000):
        function_f(x, y)
    function_d()

def function_b(x, y):
    for i in range(10000):
        function_f(x, y)
    function_d()
    function_d()

def function_c(x, y):
    for i in range(10000):
        function_f(x, y)
    function_d()
    function_d()
    function_d()

def function_d():
    time.sleep(0.01)

def function_f(x, y):
    x * y


if __name__ == "__main__":
    repeats = 1000
    X = 123.123
    Y = 43.432
    for function in ("function_a", "function_b",
                     "function_c"):
        cProfile.run("for i in range(1000): {0}(X, Y)"
                     .format(function))
         10003003 function calls in 16.040 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.007    0.007   16.040   16.040 <string>:1(<module>)
     1000    3.878    0.004   16.033    0.016 modeltest.py:13(function_a)
     1000    0.006    0.000   10.241    0.010 modeltest.py:31(function_d)
 10000000    1.915    0.000    1.915    0.000 modeltest.py:34(function_f)
        1    0.000    0.000   16.040   16.040 {built-in method builtins.exec}
     1000   10.235    0.010   10.235    0.010 {built-in method time.sleep}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}


         10005003 function calls in 28.183 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.008    0.008   28.183   28.183 <string>:1(<module>)
     1000    4.873    0.005   28.175    0.028 modeltest.py:18(function_b)
     2000    0.015    0.000   20.903    0.010 modeltest.py:31(function_d)
 10000000    2.399    0.000    2.399    0.000 modeltest.py:34(function_f)
        1    0.000    0.000   28.183   28.183 {built-in method builtins.exec}
     2000   20.887    0.010   20.887    0.010 {built-in method time.sleep}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}


         10007003 function calls in 38.968 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.008    0.008   38.968   38.968 <string>:1(<module>)
     1000    5.004    0.005   38.959    0.039 modeltest.py:24(function_c)
     3000    0.024    0.000   31.498    0.010 modeltest.py:31(function_d)
 10000000    2.457    0.000    2.457    0.000 modeltest.py:34(function_f)
        1    0.000    0.000   38.968   38.968 {built-in method builtins.exec}
     3000   31.474    0.010   31.474    0.010 {built-in method time.sleep}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}

ncalls: the number of calls to
tottime: total time spent in a function, but sent other internal functions of time spent in a function call
percall: tottime / ncalls average time of each call function
cumtime: cumulative time list the time spent in a function, function calls and other functions include time spent inside
PerCall (second): shows the average time of a function call, a function of wrapping time consuming it invokes

Reproduced in: https: //www.jianshu.com/p/9d44767f913c

Guess you like

Origin blog.csdn.net/weixin_33895695/article/details/91278543