Skip to main content

A poor man's debugger for Python.

Project description

PySnooper - Never use print for debugging again

Travis CI

PySnooper is a poor man's debugger.

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.

You want to know which lines are running and which aren't, and what the values of the local variables are.

Most people would use print lines, in strategic locations, some of them showing the values of variables.

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

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:

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)

The output to stderr is:

Source path:... /my_code/foo.py
Starting var:.. number = 6
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 = []
New var:....... bits = []
15:29:11.327032 line         7         while number:
15:29:11.327032 line         8             number, remainder = divmod(number, 2)
New var:....... remainder = 0
Modified var:.. number = 3
15:29:11.327032 line         9             bits.insert(0, remainder)
Modified var:.. bits = [0]
15:29:11.327032 line         7         while number:
15:29:11.327032 line         8             number, remainder = divmod(number, 2)
Modified var:.. number = 1
Modified var:.. remainder = 1
15:29:11.327032 line         9             bits.insert(0, remainder)
Modified var:.. bits = [1, 0]
15:29:11.327032 line         7         while number:
15:29:11.327032 line         8             number, remainder = divmod(number, 2)
Modified var:.. number = 0
15:29:11.327032 line         9             bits.insert(0, remainder)
Modified var:.. bits = [1, 1, 0]
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]

Or if you don't want to trace an entire function, you can wrap the relevant part in a with block:

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)

Features

If stderr is not easily accessible for you, you can redirect the output to a file:

@pysnooper.snoop('/my/log/file.log')

You can also pass a stream or a callable instead, and they'll be used.

See values of some expressions that aren't local variables:

@pysnooper.snoop(watch=('foo.bar', 'self.x["whatever"]'))

Expand values to see all their attributes or items of lists/dictionaries:

@pysnooper.snoop(watch_explode=('foo', 'self'))

This will output lines like:

Modified var:.. foo[2] = 'whatever'
New var:....... self.baz = 8

(see Advanced Usage for more control)

Show snoop lines for functions that your function calls:

@pysnooper.snoop(depth=2)

Start all snoop lines with a prefix, to grep for them easily:

@pysnooper.snoop(prefix='ZZZ ')

On multi-threaded apps identify which thread are snooped in output:

@pysnooper.snoop(thread_info=True)

PySnooper supports decorating generators.

If you decorate a class with snoop, it'll automatically apply the decorator to all the methods. (Not including properties and other special cases.)

You can also customize the repr of an object:

def large(l):
    return isinstance(l, list) and len(l) > 5

def print_list_size(l):
    return 'list(size={})'.format(len(l))

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)))
def sum_to_x(x):
    l = list(range(x))
    a = numpy.zeros((10,10))
    return sum(l)

sum_to_x(10000)

You will get l = list(size=10000) for the list, and a = ndarray(shape=(10, 10), dtype=float64) for the ndarray. The custom_repr are matched in order, if one condition matches, no further conditions will be checked.

Variables and exceptions get truncated to 100 characters by default. You can customize that:

    @pysnooper.snoop(max_variable_length=200)

You can also use max_variable_length=None to never truncate them.

Installation

You can install PySnooper by:

  • pip:
$ pip install pysnooper
  • conda with conda-forge channel:
$ conda install -c conda-forge pysnooper

Advanced Usage

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:

import pysnooper

@pysnooper.snoop(watch=(
    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:].

$ export PYSNOOPER_DISABLED=1 # This makes PySnooper not do any snooping

License

Copyright (c) 2019 Ram Rachum and collaborators, released under the MIT license.

I provide Development services in Python and Django and I give Python workshops to teach people Python and related topics.

Media Coverage

Hacker News thread and /r/Python Reddit thread (22 April 2019)

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

PySnooper-0.2.8.tar.gz (41.7 kB view details)

Uploaded Source

Built Distribution

PySnooper-0.2.8-py2.py3-none-any.whl (14.5 kB view details)

Uploaded Python 2 Python 3

File details

Details for the file PySnooper-0.2.8.tar.gz.

File metadata

  • Download URL: PySnooper-0.2.8.tar.gz
  • Upload date:
  • Size: 41.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/1.13.0 pkginfo/1.5.0.1 requests/2.21.0 setuptools/41.0.1 requests-toolbelt/0.9.1 tqdm/4.31.1 CPython/3.7.2

File hashes

Hashes for PySnooper-0.2.8.tar.gz
Algorithm Hash digest
SHA256 845cc1c160d5f4b6f9ba62d6fadc2f7f51daa8759a34c93dee286a94dd77c3a9
MD5 050f15093c5346ef7786878e367623c9
BLAKE2b-256 3e5642899d5c12393855911042cce9d38b150db7913484125ba3463b8b4a5612

See more details on using hashes here.

File details

Details for the file PySnooper-0.2.8-py2.py3-none-any.whl.

File metadata

  • Download URL: PySnooper-0.2.8-py2.py3-none-any.whl
  • Upload date:
  • Size: 14.5 kB
  • Tags: Python 2, Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/1.13.0 pkginfo/1.5.0.1 requests/2.21.0 setuptools/41.0.1 requests-toolbelt/0.9.1 tqdm/4.31.1 CPython/3.7.2

File hashes

Hashes for PySnooper-0.2.8-py2.py3-none-any.whl
Algorithm Hash digest
SHA256 9db476d6d92284e5f1fef557c193dc378327dbe6aeb6eea6b044013a006f221b
MD5 030edf63f8aa9507888624ad07a1dd98
BLAKE2b-256 d219867e28f23fe58688be138ec9da850a16c397b228d0d2b3f73519943bf00d

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page