2019-04-19 22:20:27 +00:00
# PySnooper - Never use print for debugging again #
2019-04-21 13:53:18 +00:00
[](https://travis-ci.org/cool-RR/PySnooper)
2019-04-21 18:39:32 +00:00
**PySnooper** is a poor man's debugger.
2019-04-19 22:20:27 +00:00
You're trying to figure out why your Python code isn't doing what you think it should be doing. You'd love to use a full-fledged debugger with breakpoints and watches, but you can't be bothered to set one up right now.
2019-04-21 19:03:36 +00:00
You want to know which lines are running and which aren't, and what the values of the local variables are.
2019-04-19 22:20:27 +00:00
2019-04-21 19:03:36 +00:00
Most people would use `print` lines, in strategic locations, some of them showing the values of variables.
2019-04-19 22:20:27 +00:00
**PySnooper** lets you do the same, except instead of carefully crafting the right `print` lines, you just add one decorator line to the function you're interested in. You'll get a play-by-play log of your function, including which lines ran and when, and exactly when local variables were changed.
What makes **PySnooper** stand out from all other code intelligence tools? You can use it in your shitty, sprawling enterprise codebase without having to do any setup. Just slap the decorator on, as shown below, and redirect the output to a dedicated log file by specifying its path as the first argument.
# Example #
2019-04-22 13:55:31 +00:00
We're writing a function that converts a number to binary, by returning a list of bits. Let's snoop on it by adding the `@pysnooper.snoop()` decorator:
2019-04-23 17:30:21 +00:00
2019-04-22 22:05:53 +00:00
```python
import pysnooper
@pysnooper .snoop()
def number_to_bits(number):
if number:
bits = []
while number:
number, remainder = divmod(number, 2)
bits.insert(0, remainder)
return bits
else:
return [0]
number_to_bits(6)
```
2019-04-21 18:39:32 +00:00
The output to stderr is:
2019-04-23 17:30:21 +00:00
```
2019-08-09 15:09:56 +00:00
Source path:... /my_code/foo.py
2019-04-23 17:30:21 +00:00
Starting var:.. number = 6
2019-04-24 12:29:41 +00:00
15:29:11.327032 call 4 def number_to_bits(number):
15:29:11.327032 line 5 if number:
15:29:11.327032 line 6 bits = []
2019-04-23 17:30:21 +00:00
New var:....... bits = []
2019-04-24 12:29:41 +00:00
15:29:11.327032 line 7 while number:
15:29:11.327032 line 8 number, remainder = divmod(number, 2)
2019-04-23 17:30:21 +00:00
New var:....... remainder = 0
Modified var:.. number = 3
2019-04-24 12:29:41 +00:00
15:29:11.327032 line 9 bits.insert(0, remainder)
2019-04-23 17:30:21 +00:00
Modified var:.. bits = [0]
2019-04-24 12:29:41 +00:00
15:29:11.327032 line 7 while number:
15:29:11.327032 line 8 number, remainder = divmod(number, 2)
2019-04-23 17:30:21 +00:00
Modified var:.. number = 1
Modified var:.. remainder = 1
2019-04-24 12:29:41 +00:00
15:29:11.327032 line 9 bits.insert(0, remainder)
2019-04-23 17:30:21 +00:00
Modified var:.. bits = [1, 0]
2019-04-24 12:29:41 +00:00
15:29:11.327032 line 7 while number:
15:29:11.327032 line 8 number, remainder = divmod(number, 2)
2019-04-23 17:30:21 +00:00
Modified var:.. number = 0
2019-04-24 12:29:41 +00:00
15:29:11.327032 line 9 bits.insert(0, remainder)
2019-04-23 17:30:21 +00:00
Modified var:.. bits = [1, 1, 0]
2019-04-24 12:29:41 +00:00
15:29:11.327032 line 7 while number:
15:29:11.327032 line 10 return bits
15:29:11.327032 return 10 return bits
Return value:.. [1, 1, 0]
2019-04-23 17:30:21 +00:00
```
2019-04-19 22:20:27 +00:00
2019-05-05 07:41:18 +00:00
Or if you don't want to trace an entire function, you can wrap the relevant part in a `with` block:
```python
import pysnooper
import random
def foo():
lst = []
for i in range(10):
lst.append(random.randrange(1, 1000))
with pysnooper.snoop():
lower = min(lst)
upper = max(lst)
mid = (lower + upper) / 2
print(lower, mid, upper)
foo()
```
which outputs something like:
```
New var:....... i = 9
New var:....... lst = [681, 267, 74, 832, 284, 678, ...]
09:37:35.881721 line 10 lower = min(lst)
New var:....... lower = 74
09:37:35.882137 line 11 upper = max(lst)
New var:....... upper = 832
09:37:35.882304 line 12 mid = (lower + upper) / 2
74 453.0 832
New var:....... mid = 453.0
09:37:35.882486 line 13 print(lower, mid, upper)
```
2019-04-19 22:20:27 +00:00
# Features #
2019-04-21 19:25:12 +00:00
If stderr is not easily accessible for you, you can redirect the output to a file:
2019-04-19 22:20:27 +00:00
2019-04-23 17:30:21 +00:00
```python
@pysnooper .snoop('/my/log/file.log')
```
2019-04-21 18:39:32 +00:00
2019-05-03 15:25:15 +00:00
You can also pass a stream or a callable instead, and they'll be used.
2019-05-03 15:31:39 +00:00
See values of some expressions that aren't local variables:
2019-04-19 22:20:27 +00:00
2019-04-23 17:30:21 +00:00
```python
2019-05-03 15:31:39 +00:00
@pysnooper .snoop(watch=('foo.bar', 'self.x["whatever"]'))
2019-04-23 17:30:21 +00:00
```
2019-04-21 18:39:32 +00:00
2019-05-03 15:22:41 +00:00
Expand values to see all their attributes or items of lists/dictionaries:
```python
2019-05-03 15:58:53 +00:00
@pysnooper .snoop(watch_explode=('foo', 'self'))
2019-05-03 15:22:41 +00:00
```
2019-05-03 18:44:06 +00:00
This will output lines like:
```
Modified var:.. foo[2] = 'whatever'
2019-05-03 18:50:54 +00:00
New var:....... self.baz = 8
2019-05-03 18:44:06 +00:00
```
2019-05-03 15:22:41 +00:00
(see [Advanced Usage ](#advanced-usage ) for more control)
2019-04-21 18:48:27 +00:00
Show snoop lines for functions that your function calls:
2019-04-23 17:30:21 +00:00
```python
@pysnooper .snoop(depth=2)
```
2019-04-21 18:48:27 +00:00
Start all snoop lines with a prefix, to grep for them easily:
2019-04-23 17:30:21 +00:00
```python
@pysnooper .snoop(prefix='ZZZ ')
```
2019-04-21 18:39:32 +00:00
2019-05-28 16:53:59 +00:00
On multi-threaded apps identify which thread are snooped in output:
2019-05-08 13:52:21 +00:00
```python
@pysnooper .snoop(thread_info=True)
```
2019-05-11 11:50:23 +00:00
PySnooper supports decorating generators.
2019-09-08 11:32:32 +00:00
If the decorator is applied to a class, PySnooper will decorate all of its instance methods. If you use this feature, you should be aware
of the behaviour when snooping on decorated functions:
* If any methods have existing decorators, and those decorators return functions, it is the decorated method that will be snooped.
* Some decorators do not return functions (rather, they return an instance of a class with a `__call__` method).
This includes the `@property` builtin. In these cases, the decorated method will not be snooped at all.
2019-05-28 16:53:59 +00:00
You can also customize the repr of an object:
```python
def large(l):
return isinstance(l, list) and len(l) > 5
def print_list_size(l):
return 'list(size={})'.format(len(l))
2019-05-29 15:10:17 +00:00
def print_ndarray(a):
return 'ndarray(shape={}, dtype={})'.format(a.shape, a.dtype)
@pysnooper .snoop(custom_repr=((large, print_list_size), (numpy.ndarray, print_ndarray)))
2019-05-28 16:53:59 +00:00
def sum_to_x(x):
l = list(range(x))
2019-05-29 15:10:17 +00:00
a = numpy.zeros((10,10))
2019-05-28 16:53:59 +00:00
return sum(l)
sum_to_x(10000)
```
2019-05-29 15:10:17 +00:00
You will get `l = list(size=10000)` for the list, and `a = ndarray(shape=(10, 10), dtype=float64)` for the ndarray.
2019-05-29 15:16:04 +00:00
The `custom_repr` are matched in order, if one condition matches, no further conditions will be checked.
2019-05-11 11:50:23 +00:00
2019-04-21 18:39:32 +00:00
# Installation #
2019-04-19 22:20:27 +00:00
2019-05-09 23:44:55 +00:00
You can install **PySnooper** by:
* pip:
2019-04-23 17:30:21 +00:00
```console
$ pip install pysnooper
2019-05-09 23:44:55 +00:00
```
* conda with conda-forge channel:
```console
$ conda install -c conda-forge pysnooper
2019-04-23 17:30:21 +00:00
```
2019-05-03 15:22:41 +00:00
# Advanced Usage #
2019-05-03 15:58:53 +00:00
`watch_explode` will automatically guess how to expand the expression passed to it based on its class. You can be more specific by using one of the following classes:
2019-05-03 15:22:41 +00:00
```python
import pysnooper
2019-05-03 16:00:14 +00:00
@pysnooper .snoop(watch=(
2019-05-03 15:22:41 +00:00
pysnooper.Attrs('x'), # attributes
pysnooper.Keys('y'), # mapping (e.g. dict) items
pysnooper.Indices('z'), # sequence (e.g. list/tuple) items
))
```
Exclude specific keys/attributes/indices with the `exclude` parameter, e.g. `Attrs('x', exclude=('_foo', '_bar'))` .
Add a slice after `Indices` to only see the values within that slice, e.g. `Indices('z')[-3:]` .
2019-06-18 18:21:51 +00:00
```console
$ export PYSNOOPER_DISABLED=1 # This makes PySnooper not do any snooping
```
2019-06-17 15:57:17 +00:00
2019-04-23 17:30:21 +00:00
# License #
2019-04-19 22:20:27 +00:00
2019-04-24 09:10:46 +00:00
Copyright (c) 2019 Ram Rachum and collaborators, released under the MIT license.
2019-04-21 19:15:44 +00:00
2019-04-23 17:30:21 +00:00
I provide [Development services in Python and Django](https://chipmunkdev.com
) and I [give Python workshops ](http://pythonworkshops.co/ ) to teach people
Python and related topics.
2019-04-25 11:38:14 +00:00
2019-04-25 12:00:33 +00:00
# Media Coverage #
2019-04-25 11:38:14 +00:00
2019-04-25 12:00:33 +00:00
[Hacker News thread ](https://news.ycombinator.com/item?id=19717786 )
and [/r/Python Reddit thread ](https://www.reddit.com/r/Python/comments/bg0ida/pysnooper_never_use_print_for_debugging_again/ ) (22 April 2019)