A poor man's debugger for Python that provides elegant tracing without print statements.
npx @tessl/cli install tessl/pypi-pysnooper@1.2.0A poor man's debugger for Python that provides elegant tracing without print statements. PySnooper offers an alternative to traditional debugging with breakpoints by decorating functions or wrapping code blocks to get detailed execution logs showing line-by-line execution, variable changes, and timing information.
pip install pysnooperimport pysnooperimport pysnooper
# Decorator usage - trace entire function
@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]
# Context manager usage - trace code block
def analyze_data():
data = [1, 2, 3, 4, 5]
with pysnooper.snoop():
minimum = min(data)
maximum = max(data)
average = sum(data) / len(data)
return minimum, maximum, average
# Output redirection to file
@pysnooper.snoop('/tmp/debug.log')
def process_data(items):
result = []
for item in items:
result.append(item * 2)
return resultThe primary debugging interface that can be used as both a decorator and context manager.
class snoop:
def __init__(
self,
output=None,
watch=(),
watch_explode=(),
depth=1,
prefix='',
overwrite=False,
thread_info=False,
custom_repr=(),
max_variable_length=100,
normalize=False,
relative_time=False,
color=True
):
"""
Snoop on function execution, writing detailed logs to output.
Args:
output: Output destination - None (stderr), file path, stream, or callable
watch (tuple): Expressions to watch that aren't local variables
watch_explode (tuple): Expressions to expand/explode (show attributes/items)
depth (int): Tracing depth for nested function calls (default: 1)
prefix (str): Prefix string for all snoop output lines
overwrite (bool): Whether to overwrite output file (default: False)
thread_info (bool): Include thread information in output
custom_repr (tuple): Custom representation functions for objects
max_variable_length (int): Maximum length for variable representations (default: 100)
normalize (bool): Remove machine-specific data (paths, addresses, timestamps)
relative_time (bool): Show timestamps relative to start time
color (bool): Enable colored output (default: True on Linux/macOS)
"""
def __call__(self, function_or_class):
"""Use as decorator on functions or classes."""
def __enter__(self):
"""Use as context manager - enter tracing block."""
def __exit__(self, exc_type, exc_value, exc_traceback):
"""Use as context manager - exit tracing block."""Helper classes for advanced variable watching and expansion.
class Attrs:
def __init__(self, source, exclude=()):
"""
Watch object attributes.
Args:
source (str): Variable expression to watch
exclude (tuple): Attribute names to exclude from watching
"""
class Keys:
def __init__(self, source, exclude=()):
"""
Watch dictionary/mapping keys and values.
Args:
source (str): Variable expression to watch
exclude (tuple): Keys to exclude from watching
"""
class Indices:
def __init__(self, source, exclude=()):
"""
Watch sequence indices and values with optional slicing.
Args:
source (str): Variable expression to watch
exclude (tuple): Indices to exclude from watching
"""
def __getitem__(self, slice_obj):
"""
Enable slicing syntax like Indices('list_var')[1:5].
Args:
slice_obj (slice): Slice object for limiting indices
Returns:
Indices: New Indices instance with slice applied
"""
class Exploding:
def __init__(self, source, exclude=()):
"""
Smart variable expander - automatically determines expansion method.
Args:
source (str): Variable expression to watch
exclude (tuple): Items to exclude from watching
Behavior:
- Uses Keys for mappings (dict-like objects)
- Uses Indices for sequences (list-like objects)
- Uses Attrs for other objects
"""Version information for the package.
__version__: str
# Version string (e.g., '1.2.3')
__version_info__: NamedTuple
# Structured version information with major, minor, micro fieldsRedirect tracing output to a file:
@pysnooper.snoop('/my/log/file.log')
def process_data(data):
return [x * 2 for x in data]Monitor specific expressions that aren't local variables:
@pysnooper.snoop(watch=('len(data)', 'data[0]', 'self.status'))
def analyze_list(self, data):
# Will show values of watched expressions
result = sum(data)
return resultExpand objects to see their attributes or items:
# Automatic expansion
@pysnooper.snoop(watch_explode=('obj', 'data_dict'))
def process_object(obj, data_dict):
return obj.process(data_dict)
# Specific expansion types
@pysnooper.snoop(watch=(
pysnooper.Attrs('obj'), # Object attributes
pysnooper.Keys('data_dict'), # Dictionary keys/values
pysnooper.Indices('items')[2:5], # List items with slicing
))
def detailed_processing(obj, data_dict, items):
return obj.transform(data_dict, items)Show snoop lines for functions called within traced functions:
@pysnooper.snoop(depth=2)
def main_function():
helper_function() # This will also be traced
return "done"
def helper_function():
x = 42
return x * 2Identify threads in multi-threaded applications:
@pysnooper.snoop(thread_info=True)
def worker_function(task_id):
# Output will include thread information
return process_task(task_id)Customize how values are displayed:
def custom_list_repr(obj):
if isinstance(obj, list) and len(obj) > 10:
return f'list(len={len(obj)})'
return repr(obj)
@pysnooper.snoop(
custom_repr=((lambda x: isinstance(x, list), custom_list_repr),),
max_variable_length=200,
prefix='DEBUG: ',
color=False
)
def process_large_data(big_list):
return sum(big_list)Trace all methods in a class:
@pysnooper.snoop()
class DataProcessor:
def __init__(self, data):
self.data = data
def process(self):
return [x * 2 for x in self.data]
def analyze(self):
return sum(self.data)Disable all PySnooper tracing globally:
export PYSNOOPER_DISABLED=1When set, all @pysnooper.snoop() decorators and context managers become no-ops.
PySnooper is designed to be non-intrusive. If tracing encounters errors:
The traced code continues running normally even if tracing fails.
PySnooper automatically detects and properly handles generator functions:
@pysnooper.snoop()
def fibonacci_generator(n):
a, b = 0, 1
for _ in range(n):
yield a
a, b = b, a + bWorks with lambda functions and dynamically created functions:
# Trace lambda execution
traced_lambda = pysnooper.snoop()(lambda x: x * 2)
result = traced_lambda(5)Context manager usage shows elapsed time for code blocks:
def analyze_performance():
data = list(range(1000))
with pysnooper.snoop():
# This block will show execution time
result = sum(x * x for x in data)
return result