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.
Table of Contents
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!
Leave a Reply