Repository: cool-RR/PySnooper

Issue:
Suggestion: "pydoc pysnooper" should show how to use this

Comments:

Patch:
diff --git a/pysnooper/__init__.py b/pysnooper/__init__.py
index ef8dfef..4049c67 100644
--- a/pysnooper/__init__.py
+++ b/pysnooper/__init__.py
@@ -1,5 +1,20 @@
 # Copyright 2019 Ram Rachum and collaborators.
 # This program is distributed under the MIT license.
+"""PySnooper - Never use print for debugging again
+
+Usage:
+
+    import pysnooper
+
+    @pysnooper.snoop()
+    def number_to_bits(number):
+        ...
+
+A log will be written to stderr showing the lines executed and variables
+changed in the decorated function.
+
+For more information, see https://github.com/cool-RR/PySnooper
+"""
 
 from .pysnooper import snoop
 from .variables import Attrs, Exploding, Indices, Keys
diff --git a/pysnooper/variables.py b/pysnooper/variables.py
index 6a97666..f3db008 100644
--- a/pysnooper/variables.py
+++ b/pysnooper/variables.py
@@ -7,11 +7,22 @@ from . import utils
 from . import pycompat
 
 
+def needs_parentheses(source):
+    def code(s):
+        return compile(s, '<variable>', 'eval').co_code
+
+    return code('{}.x'.format(source)) != code('({}).x'.format(source))
+
+
 class BaseVariable(pycompat.ABC):
     def __init__(self, source, exclude=()):
         self.source = source
         self.exclude = utils.ensure_tuple(exclude)
         self.code = compile(source, '<variable>', 'eval')
+        if needs_parentheses(source):
+            self.unambiguous_source = '({})'.format(source)
+        else:
+            self.unambiguous_source = source
 
     def items(self, frame):
         try:
@@ -36,7 +47,7 @@ class CommonVariable(BaseVariable):
             except Exception:
                 continue
             result.append((
-                '({}){}'.format(self.source, self._format_key(key)),
+                '{}{}'.format(self.unambiguous_source, self._format_key(key)),
                 utils.get_shortish_repr(value)
             ))
         return result


Test patch:
diff --git a/tests/test_pysnooper.py b/tests/test_pysnooper.py
index f5ade3d..c9586b8 100644
--- a/tests/test_pysnooper.py
+++ b/tests/test_pysnooper.py
@@ -9,6 +9,7 @@ from python_toolbox import sys_tools, temp_file_tools
 import pytest
 
 import pysnooper
+from pysnooper.variables import needs_parentheses
 from .utils import (assert_output, VariableEntry, CallEntry, LineEntry,
                     ReturnEntry, OpcodeEntry, ReturnValueEntry, ExceptionEntry)
 
@@ -132,7 +133,7 @@ def test_watch_explode():
             self.y = y
 
 
-    @pysnooper.snoop(watch_explode=('_d', '_point', 'lst'))
+    @pysnooper.snoop(watch_explode=('_d', '_point', 'lst + []'))
     def my_function():
         _d = {'a': 1, 'b': 2, 'c': 'ignore'}
         _point = Foo(x=3, y=4)
@@ -150,22 +151,24 @@ def test_watch_explode():
             VariableEntry('Foo'),
             CallEntry('def my_function():'),
             LineEntry(),
-            VariableEntry("(_d)['a']", '1'),
-            VariableEntry("(_d)['b']", '2'),
-            VariableEntry("(_d)['c']", "'ignore'"),
             VariableEntry('_d'),
+            VariableEntry("_d['a']", '1'),
+            VariableEntry("_d['b']", '2'),
+            VariableEntry("_d['c']", "'ignore'"),
             LineEntry(),
-            VariableEntry('(_point).x', '3'),
-            VariableEntry('(_point).y', '4'),
             VariableEntry('_point'),
+            VariableEntry('_point.x', '3'),
+            VariableEntry('_point.y', '4'),
             LineEntry(),
-            VariableEntry('(lst)[0]', '7'),
-            VariableEntry('(lst)[1]', '8'),
-            VariableEntry('(lst)[2]', '9'),
+            VariableEntry('(lst + [])[0]', '7'),
+            VariableEntry('(lst + [])[1]', '8'),
+            VariableEntry('(lst + [])[2]', '9'),
             VariableEntry('lst'),
+            VariableEntry('lst + []'),
             LineEntry(),
-            VariableEntry('(lst)[3]', '10'),
+            VariableEntry('(lst + [])[3]', '10'),
             VariableEntry('lst'),
+            VariableEntry('lst + []'),
             ReturnEntry(),
             ReturnValueEntry('None')
         )
@@ -202,18 +205,18 @@ def test_variables_classes():
             VariableEntry('WithSlots'),
             CallEntry('def my_function():'),
             LineEntry(),
-            VariableEntry("(_d)['a']", '1'),
-            VariableEntry("(_d)['b']", '2'),
             VariableEntry('_d'),
+            VariableEntry("_d['a']", '1'),
+            VariableEntry("_d['b']", '2'),
             LineEntry(),
-            VariableEntry('(_s).x', '3'),
-            VariableEntry('(_s).y', '4'),
             VariableEntry('_s'),
+            VariableEntry('_s.x', '3'),
+            VariableEntry('_s.y', '4'),
             LineEntry(),
-            VariableEntry('(_lst)[997]', '997'),
-            VariableEntry('(_lst)[998]', '998'),
-            VariableEntry('(_lst)[999]', '999'),
             VariableEntry('_lst'),
+            VariableEntry('_lst[997]', '997'),
+            VariableEntry('_lst[998]', '998'),
+            VariableEntry('_lst[999]', '999'),
             ReturnEntry(),
             ReturnValueEntry('None')
         )
@@ -613,3 +616,19 @@ def test_error_in_overwrite_argument():
                 y = 8
                 return y + x
 
+
+def test_needs_parentheses():
+    assert not needs_parentheses('x')
+    assert not needs_parentheses('x.y')
+    assert not needs_parentheses('x.y.z')
+    assert not needs_parentheses('x.y.z[0]')
+    assert not needs_parentheses('x.y.z[0]()')
+    assert not needs_parentheses('x.y.z[0]()(3, 4 * 5)')
+    assert not needs_parentheses('foo(x)')
+    assert not needs_parentheses('foo(x+y)')
+    assert not needs_parentheses('(x+y)')
+    assert not needs_parentheses('[x+1 for x in ()]')
+    assert needs_parentheses('x + y')
+    assert needs_parentheses('x * y')
+    assert needs_parentheses('x and y')
+    assert needs_parentheses('x if z else y')