# coding: utf-8 # # Table of Contents #

1  "Living in a noisy world...", using James Powell's (dutc) rwatch module
1.1  Requirements and links
1.2  Defining a debugging context manager, just to try
1.2.1  Watching just one object
1.2.2  Can we delete the rwatch ?
1.2.3  More useful debuggin information
1.2.4  Watching any object
1.2.5  A first context manager to have debugging for one object
1.2.6  A second context manager to debug any object
1.3  Defining a context manager to add white noise
1.3.1  Capturing any numerical value
1.3.2  Adding a white noise for numbers
1.3.3  WhiteNoiseComplex context manager
1.4  Defining a generic noisy context manager
1.5  Conclusion
# # *"Living in a noisy world..."*, using James Powell's (`dutc`) `rwatch` module # # Goal : I want to write a [context manager](https://docs.python.org/3.5/library/stdtypes.html#typecontextmanager) for [Python 3.5+](https://www.Python.org/), so that inside the context manager, every number is seen noisy (with a white Gaussian noise, for instance). # # > It will be like being drunk, except that your it will be my Python interpretor and not me ! # # For instance, I will like to have this feature: # ```python # >>> x = 120193 # >>> print(x) # 120193 # >>> np.random.seed(1234) # >>> with WhiteNoise(): # >>> print(x) # 120193.47143516373249306 # ``` # ---- # ## Requirements and links # First, we will need [numpy](https://docs.scipy.org/doc/numpy/user/whatisnumpy.html) to have some random number generator, as `numpy.random.normal` to have some 1D Gaussian noise. # In[1]: import numpy as np # In[2]: np.random.seed(1234) np.random.normal() # Then, the core part will be to install and import [James Powell (`dutc`)](https://github.com/dutc/) [`rwatch`](https://github.com/dutc/rwatch) module. # If you don't have it installed : # # 1. Be sure to have CPython 3.5. `rwatch` patches the CPython eval loop, so it's fixed to specific versions of Python & the author lazy about keeping it updated. # 2. Then `pip install dutc-rwatch`. It should work, but it fails for me. # 3. (alternative) You can just `cd /tmp/ && git clone https://github.com/dutc/rwatch && cd rwatch/src/ && make` and copy the `rwatch.so` dynamic library wherever you need... # In[5]: get_ipython().run_cell_magic('bash', '', 'tmpdir=$(mktemp -d)\ncd $tmpdir\ngit clone https://github.com/dutc/rwatch\ncd rwatch/src/\nmake\nls -larth ./rwatch.so\nfile ./rwatch.so\n# cp ./rwatch.so /where/ver/you/need/ # ~/publis/notebook/ for me') # Anyhow, if `rwatch` is installed, we can import it, and it enables two new functions in the `sys` module: # In[6]: import rwatch from sys import setrwatch, getrwatch setrwatch({}) # clean any previously installed rwatch getrwatch() # Finally, we need the [`collections`](https://docs.python.org/3/library/collections.html) module and its [`defaultdict`](https://docs.python.org/3/library/collections.html#collections.defaultdict) magical datastructure. # In[7]: from collections import defaultdict # ---- # ## Defining a debugging context manager, just to try # # This is the first example given in [James presentation]() at PyCon Canada 2016. # # We will first define and add a `rwatch` for just one object, let say a variable `x`, and then for any object using `defaultdict`. # From there, writing a context manager that enables this feature only locally is easy. # ### Watching just one object # 1. Write the function, that needs two argument `frame, obj`, and should return `obj`, # 2. Install it... # 3. Check it! # In[15]: def basic_view(frame, obj): print("Python saw the object {} from frame {}".format(obj, frame)) return obj # In[16]: x = "I am alive!" # In[17]: setrwatch({ id(x): basic_view }) # In[18]: print(x) # That's awesome, it works! # ### Can we delete the `rwatch` ? # Sure! # In[19]: def delrwatch(idobj): getrwatch().pop(idobj, None) # In[20]: print(x) delrwatch(id(x)) print(x) # no more rwatch on this! print(x) # no more rwatch on this! # We can also delete rwatches that are not defined, without a failure: # In[21]: y = "I am Zorro !" print(y) delrwatch(y) # No issue! print(y) # ### More useful debuggin information # What is this `frame` thing? # It is described in the documentation of the [`inspect`](https://docs.python.org/3/library/inspect.html) module. # # We can actually use it to display some useful information about the object and where was it called etc. # In[22]: from inspect import getframeinfo def debug_view(frame, obj): info = getframeinfo(frame) msg = '- Access to {!r} (@{}) at {}:{}:{}' print(msg.format(obj, hex(id(obj)), info.filename, info.lineno, info.function)) return obj # In[23]: setrwatch({}) setrwatch({ id(x): debug_view }) getrwatch() # In[24]: print(x) # That can be quite useful! # ### Watching *any* object # We can actually pass a `defaultdict` to the `setrwatch` function, so that *any* object will have a rwatch! # # > **Warning**: obviously, this will crazily slowdown your interpreter! # # So let be cautious, and only deal with strings here. # # But I want to be safe, so it will only works if the frame indicate that the variable does not come from a file. # In[25]: setrwatch({}) # In[26]: def debug_view_for_str(frame, obj): if isinstance(obj, str): info = getframeinfo(frame) if '' in info.filename or '' in info.filename or ':2:`, showing the access in line `#2` of the constant `0`. # In[58]: with InspectAllObjects(): print("Darth Vader -- No Luke, I am your Father!") print("Luke -- I have a father? Yay! Let's eat cookies together!") # We also see here the `None` and `{}` objects being given to the context manager (see the `__enter__` method at first, and `__exit__` at the end). # ---- # ## Defining a context manager to add white noise # # Basically, we will do as above, but instead of debug information, a white noise sampled from a Normal distribution (i.e., $\sim \mathcal{N}(0, 1)$) will be *added* to any number. # ### Capturing any numerical value # To capture both integers and float numbers, the [`numbers.Number`](https://docs.python.org/2/library/numbers.html#numbers.Number) abstract class is useful. # In[59]: from numbers import Number # ### Adding a white noise for numbers # This is very simple. # # But I want to be safe, so it will only works if the frame indicate that the number does not come from a file, as previously. # In[60]: def add_white_noise_to_numbers(frame, obj): if isinstance(obj, Number): info = getframeinfo(frame) if '' in info.filename or '' in info.filename or ' « Now, the real world is non noisy, but the complex one is! » # # That's one sentence I thought I would never say! # ### `WhiteNoiseComplex` context manager # # To stay cautious, I only add noise to complex numbers. # In[67]: class WhiteNoiseComplex(object): def __init__(self): pass def __enter__(self): setrwatch(defaultdict(lambda: add_white_noise_to_complex)) def __exit__(self, exc_type, exc_val, exc_tb): setrwatch({}) # And it works as expected: # In[69]: np.random.seed(120193) print(120193, 120193j) with WhiteNoiseComplex(): print(120193, 120193j) # Huhoo, noisy! print(120193, 120193j) print(0*1j) with WhiteNoiseComplex(): print(0*1j) # Huhoo, noisy! print(0*1j) # ---- # ## Defining a generic `noisy` context manager # # This will be a very simple change from the previous one, by letting the `Noisy` class accept *any* noisy function, which takes `obj` and return a noisy version of `obj`, only for complex-valued objects. # In[71]: class Noisy(object): def __init__(self, noise): def add_white_noise_to_complex(frame, obj): if isinstance(obj, complex): info = getframeinfo(frame) if '' in info.filename or '