Amazing Python Package Showcase (4) — PySnooper

PySnooper is a powerful yet simple tool that can save you time and effort when debugging Python code. Its ability to provide a clear and detailed trace of the code execution makes it easier to identify and fix issues.

Debugging is an essential part of the development process, and Python offers various tools and techniques to make it easier. One such tool that has gained popularity among developers is PySnooper. In this article, we’ll explore what PySnooper is, how it works, and why you should consider adding it to your debugging toolkit.

Key Features of PySnooper

Ease of Use

PySnooper is designed to be straightforward and easy to integrate into your existing codebase. With just a few lines of code, you can start tracing your functions.

Detailed Output

It provides a comprehensive view of the function execution, including variable values, line-by-line code execution, and changes in state.

Conditional Snooping

You can set conditions to trace specific parts of your code, making it efficient and less verbose.

File Logging

PySnooper allows you to log the output to a file for later analysis, which is especially useful for debugging long-running processes or batch jobs.

Customizable

It offers several customization options, allowing you to tailor the tracing output to your needs.

Advantages and Disadvantages of PySnooper

Advantages

  • Simplicity: Easy to set up and use.
  • Detailed Information: Provides a thorough trace of function execution.
  • Customization: Flexible options to tailor the tracing output.
  • File Logging: Ability to save logs for later analysis.

Disadvantages

  • Performance Overhead: Tracing can introduce a performance overhead, especially for large or complex functions.
  • Verbosity: The detailed output can be overwhelming for very large functions or deeply nested calls.
  • Limited Interactivity: Unlike interactive debuggers, PySnooper does not allow for real-time code manipulation or inspection.

Installation

Create an environment in your project using virtualenv

# create an environment
dynotes@P2021:~/projects/python$ virtualenv pysnooper
created virtual environment CPython3.10.12.final.0-64 in 775ms
creator CPython3Posix(dest=/home/dynotes/projects/python/pysnooper/pysnooper, clear=False, no_vcs_ignore=False, global=False)
seeder FromAppData(download=False, pip=bundle, setuptools=bundle, wheel=bundle, via=copy, app_data_dir=/home/dynotes/.local/share/virtualenv)
added seed packages: pip==24.1, setuptools==70.1.1, wheel==0.43.0
activators BashActivator,CShellActivator,FishActivator,NushellActivator,PowerShellActivator,PythonActivator

# activate the environment
dynotes@P2021:~/projects/python$ source ./pysnooper/bin/activate
(pysnooper) dynotes@P2021:~/projects/python$

Then install PySnooper using pip

(pysnooper) dynotes@WIN-P2021:~/projects/python$ pip install pysnooper
Collecting pysnooper
Downloading PySnooper-1.2.0-py2.py3-none-any.whl.metadata (5.2 kB)
Downloading PySnooper-1.2.0-py2.py3-none-any.whl (14 kB)
Installing collected packages: pysnooper
Successfully installed pysnooper-1.2.0

A Basic Example

Create a Python file with name of example1.py. Here’s a basic example to demonstrate its usage

import pysnooper

@pysnooper.snoop()
def factorial(n):
if n == 0:
return 1
else:
return n * factorial(n - 1)

print(factorial(5))

Run the code

(pysnooper) dynotes@P2021:~/projects/python$ python example1.py

Source path:... ~/projects/python/example1.py
Starting var:.. n = 5
17:59:11.734128 call 4 def factorial(n):
17:59:11.734592 line 5 if n == 0:
17:59:11.734730 line 8 return n * factorial(n - 1)
Starting var:.. n = 4
17:59:11.734903 call 4 def factorial(n):
17:59:11.735158 line 5 if n == 0:
17:59:11.735298 line 8 return n * factorial(n - 1)
Starting var:.. n = 3
17:59:11.735444 call 4 def factorial(n):
17:59:11.735684 line 5 if n == 0:
17:59:11.735824 line 8 return n * factorial(n - 1)
Starting var:.. n = 2
17:59:11.736013 call 4 def factorial(n):
17:59:11.736236 line 5 if n == 0:
17:59:11.736380 line 8 return n * factorial(n - 1)
Starting var:.. n = 1
17:59:11.736532 call 4 def factorial(n):
17:59:11.736755 line 5 if n == 0:
17:59:11.736863 line 8 return n * factorial(n - 1)
Starting var:.. n = 0
17:59:11.736988 call 4 def factorial(n):
17:59:11.737170 line 5 if n == 0:
17:59:11.737273 line 6 return 1
17:59:11.737382 return 6 return 1
Return value:.. 1
Elapsed time: 00:00:00.000603
17:59:11.737687 return 8 return n * factorial(n - 1)
Return value:.. 1
Elapsed time: 00:00:00.001355
17:59:11.737964 return 8 return n * factorial(n - 1)
Return value:.. 2
Elapsed time: 00:00:00.002191
17:59:11.738253 return 8 return n * factorial(n - 1)
Return value:.. 6
Elapsed time: 00:00:00.002976
17:59:11.738459 return 8 return n * factorial(n - 1)
Return value:.. 24
Elapsed time: 00:00:00.003809
17:59:11.738788 return 8 return n * factorial(n - 1)
Return value:.. 120
Elapsed time: 00:00:00.004908
120
(pysnooper) dynotes@P2021:~/projects/python$

In this example, the factorial function is decorated with @pysnooper.snoop(), which will trace its execution. When you run the code, PySnooper will provide detailed output showing the flow of execution and the values of the variables at each step.

Advanced Usage

Conditional Snooping

If you only want to trace specific conditions, you can use the watch parameter:

@pysnooper.snoop(watch=('n',))
def factorial(n):
if n == 0:
return 1
else:
return n * factorial(n - 1)

File Logging

To log the output to a file, use the output parameter:

@pysnooper.snoop(output='debug.log')
def factorial(n):
if n == 0:
return 1
else:
return n * factorial(n - 1)

This will write the tracing information to debug.log, which you can review later.

(pysnooper) dynotes@P2021:~/projects/python$ cat debug.log
Source path:... /home/dynotes/projects/python/example1.py
Starting var:.. n = 5
18:41:54.208998 call 4 def factorial(n):
18:41:54.210515 line 5 if n == 0:
18:41:54.210578 line 8 return n * factorial(n - 1)
Starting var:.. n = 4
18:41:54.210643 call 4 def factorial(n):
18:41:54.210721 line 5 if n == 0:
18:41:54.210766 line 8 return n * factorial(n - 1)
Starting var:.. n = 3
18:41:54.210821 call 4 def factorial(n):
18:41:54.210888 line 5 if n == 0:
18:41:54.210930 line 8 return n * factorial(n - 1)
Starting var:.. n = 2
18:41:54.210982 call 4 def factorial(n):
18:41:54.211049 line 5 if n == 0:
18:41:54.211092 line 8 return n * factorial(n - 1)
Starting var:.. n = 1
18:41:54.211144 call 4 def factorial(n):
18:41:54.211211 line 5 if n == 0:
18:41:54.211253 line 8 return n * factorial(n - 1)
Starting var:.. n = 0
18:41:54.211309 call 4 def factorial(n):
18:41:54.211375 line 5 if n == 0:
18:41:54.211418 line 6 return 1
18:41:54.211459 return 6 return 1
Return value:.. 1
Elapsed time: 00:00:00.000227
18:41:54.211989 return 8 return n * factorial(n - 1)
Return value:.. 1
Elapsed time: 00:00:00.000931
18:41:54.212110 return 8 return n * factorial(n - 1)
Return value:.. 2
Elapsed time: 00:00:00.001205
18:41:54.212224 return 8 return n * factorial(n - 1)
Return value:.. 6
Elapsed time: 00:00:00.001479
18:41:54.212332 return 8 return n * factorial(n - 1)
Return value:.. 24
Elapsed time: 00:00:00.001766
18:41:54.212440 return 8 return n * factorial(n - 1)
Return value:.. 120
Elapsed time: 00:00:00.003563

Practical Example: Debugging a Function to Sum Squares of Even Numbers

Source Code

Create a Python file with name of example2.py. Here’s the source code for it, with PySnooper integrated to trace the function’s execution:

import pysnooper

@pysnooper.snoop(output='debug2.log')
def sum_of_squares_of_evens(numbers):
"""
Function to calculate the sum of squares of even numbers in a list.
"""
result = 0
for number in numbers:
if number % 2 == 0:
square = number * number
result += square
return result
# Example list of numbers
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# Call the function
total = sum_of_squares_of_evens(numbers)
print(f"Sum of squares of even numbers: {total}")

The function is decorated with @pysnooper.snoop(output='debug.log') which tells PySnooper to trace the function’s execution and write the detailed output to a file named debug2.log.

(pysnooper) dynotes@P2021:~/projects/python$ cat debug2.log
Source path:... ~/projects/python/example2.py
Starting var:.. numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
18:47:50.557301 call 4 def sum_of_squares_of_evens(numbers):
18:47:50.558681 line 8 result = 0
New var:....... result = 0
18:47:50.558752 line 9 for number in numbers:
New var:....... number = 1
18:47:50.558829 line 10 if number % 2 == 0:
18:47:50.558901 line 9 for number in numbers:
Modified var:.. number = 2
18:47:50.558948 line 10 if number % 2 == 0:
18:47:50.559017 line 11 square = number * number
New var:....... square = 4
18:47:50.559065 line 12 result += square
Modified var:.. result = 4
18:47:50.559134 line 9 for number in numbers:
Modified var:.. number = 3
18:47:50.559204 line 10 if number % 2 == 0:
18:47:50.559273 line 9 for number in numbers:
Modified var:.. number = 4
18:47:50.559320 line 10 if number % 2 == 0:
18:47:50.559389 line 11 square = number * number
Modified var:.. square = 16
18:47:50.559439 line 12 result += square
Modified var:.. result = 20
18:47:50.559507 line 9 for number in numbers:
Modified var:.. number = 5
18:47:50.559575 line 10 if number % 2 == 0:
18:47:50.559644 line 9 for number in numbers:
Modified var:.. number = 6
18:47:50.559691 line 10 if number % 2 == 0:
18:47:50.559759 line 11 square = number * number
Modified var:.. square = 36
18:47:50.559806 line 12 result += square
Modified var:.. result = 56
18:47:50.559872 line 9 for number in numbers:
Modified var:.. number = 7
18:47:50.559939 line 10 if number % 2 == 0:
18:47:50.560008 line 9 for number in numbers:
Modified var:.. number = 8
18:47:50.560054 line 10 if number % 2 == 0:
18:47:50.560121 line 11 square = number * number
Modified var:.. square = 64
18:47:50.560168 line 12 result += square
Modified var:.. result = 120
18:47:50.560235 line 9 for number in numbers:
Modified var:.. number = 9
18:47:50.560309 line 10 if number % 2 == 0:
18:47:50.560377 line 9 for number in numbers:
Modified var:.. number = 10
18:47:50.560423 line 10 if number % 2 == 0:
18:47:50.560491 line 11 square = number * number
Modified var:.. square = 100
18:47:50.560539 line 12 result += square
Modified var:.. result = 220
18:47:50.560610 line 9 for number in numbers:
18:47:50.560678 line 13 return result
18:47:50.560725 return 13 return result
Return value:.. 220
Elapsed time: 00:00:00.003555

Alternatives of PySnooper

1. pdb (Python Debugger)

  • Built into Python.
  • Provides interactive debugging with breakpoints and step-through capabilities.

2. ipdb

  • An enhanced version of pdb with IPython support.
  • Offers additional features and a more user-friendly interface.

3. Loguru

  • A logging library for Python.
  • Provides simple and powerful logging capabilities.

4. PyCharm Debugger

  • Part of the PyCharm IDE.
  • Offers a rich set of debugging features including breakpoints, watches, and interactive inspection.

5. Snooping (snoop)

  • Another lightweight tracing tool.
  • Similar to PySnooper but with additional features like method tracing and enhanced output formats.

Incorporating PySnooper into your development workflow can significantly streamline your debugging process. Its ease of use, detailed output, and customization options make it a valuable addition to any Python developer’s toolkit. While it has some limitations, the advantages often outweigh them for many use cases. For more information and to download PySnooper, visit the official PySnooper page on PyPI.

Happy debugging!

You Might Also Like

Leave a Reply