From 99483a20d5c2ec93bb46cc05fb186adaff03f4b0 2012-08-21 21:28:03 From: Guy Haskin Fernald Date: 2012-08-21 21:28:03 Subject: [PATCH] Added functionality to %R and %octave magics so that -i first looks in local scope before looking in user_ns. This allows the magics to be called from within functions. Also Modified @needs_local_scope decorator for magic extensions to use a kwarg with the local namespace, so functions now get a separate kward local_ns, e.g. def magic_function(line, cell=None, local_ns=None): ... --- diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index 9e9ac82..e8c471e 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -2088,11 +2088,12 @@ class InteractiveShell(SingletonConfigurable): magic_arg_s = self.var_expand(line, stack_depth) # Put magic args in a list so we can call with f(*a) syntax args = [magic_arg_s] + kwargs = {} # Grab local namespace if we need it: if getattr(fn, "needs_local_scope", False): - args.append(sys._getframe(stack_depth).f_locals) + kwargs['local_ns'] = sys._getframe(stack_depth).f_locals with self.builtin_trap: - result = fn(*args) + result = fn(*args,**kwargs) return result def run_cell_magic(self, magic_name, line, cell): diff --git a/IPython/core/magics/execution.py b/IPython/core/magics/execution.py index 7d0b2d0..3ea8c79 100644 --- a/IPython/core/magics/execution.py +++ b/IPython/core/magics/execution.py @@ -812,7 +812,7 @@ python-profiler package from non-free.""") @skip_doctest @needs_local_scope @line_magic - def time(self,parameter_s, user_locals): + def time(self,parameter_s, local_ns=None): """Time execution of a Python statement or expression. The CPU and wall clock times are printed, and the value of the @@ -884,11 +884,11 @@ python-profiler package from non-free.""") wall_st = wtime() if mode=='eval': st = clock2() - out = eval(code, glob, user_locals) + out = eval(code, glob, local_ns) end = clock2() else: st = clock2() - exec code in glob, user_locals + exec code in glob, local_ns end = clock2() out = None wall_end = wtime() diff --git a/IPython/extensions/octavemagic.py b/IPython/extensions/octavemagic.py index a0114bf..edba5c5 100644 --- a/IPython/extensions/octavemagic.py +++ b/IPython/extensions/octavemagic.py @@ -45,7 +45,7 @@ from xml.dom import minidom from IPython.core.displaypub import publish_display_data from IPython.core.magic import (Magics, magics_class, line_magic, - line_cell_magic) + line_cell_magic, needs_local_scope) from IPython.testing.skipdoctest import skip_doctest from IPython.core.magic_arguments import ( argument, magic_arguments, parse_argstring @@ -182,12 +182,13 @@ class OctaveMagics(Magics): help='Plot format (png, svg or jpg).' ) + @needs_local_scope @argument( 'code', nargs='*', ) @line_cell_magic - def octave(self, line, cell=None): + def octave(self, line, cell=None, local_ns=None): ''' Execute code in Octave, and pull some of the results back into the Python namespace. @@ -237,18 +238,24 @@ class OctaveMagics(Magics): if cell is None: code = '' return_output = True - line_mode = True else: code = cell return_output = False - line_mode = False code = ' '.join(args.code) + code + # if there is no local namespace then default to an empty dict + if local_ns is None: + local_ns = {} + if args.input: for input in ','.join(args.input).split(','): input = unicode_to_str(input) - self._oct.put(input, self.shell.user_ns[input]) + try: + val = local_ns[input] + except KeyError: + val = self.shell.user_ns[input] + self._oct.put(input, val) # generate plots in a temporary directory plot_dir = tempfile.mkdtemp() diff --git a/IPython/extensions/rmagic.py b/IPython/extensions/rmagic.py index b4d807b..f9cf9f7 100644 --- a/IPython/extensions/rmagic.py +++ b/IPython/extensions/rmagic.py @@ -53,7 +53,7 @@ ro.conversion.py2ri = numpy2ri from IPython.core.displaypub import publish_display_data from IPython.core.magic import (Magics, magics_class, cell_magic, line_magic, - line_cell_magic) + line_cell_magic, needs_local_scope) from IPython.testing.skipdoctest import skip_doctest from IPython.core.magic_arguments import ( argument, magic_arguments, parse_argstring @@ -344,8 +344,9 @@ class RMagics(Magics): 'code', nargs='*', ) + @needs_local_scope @line_cell_magic - def R(self, line, cell=None): + def R(self, line, cell=None, local_ns=None): ''' Execute code in R, and pull some of the results back into the Python namespace. @@ -482,7 +483,8 @@ class RMagics(Magics): # arguments 'code' in line are prepended to # the cell lines - if not cell: + + if cell is None: code = '' return_output = True line_mode = True @@ -493,9 +495,17 @@ class RMagics(Magics): code = ' '.join(args.code) + code + # if there is no local namespace then default to an empty dict + if local_ns is None: + local_ns = {} + if args.input: for input in ','.join(args.input).split(','): - self.r.assign(input, self.pyconverter(self.shell.user_ns[input])) + try: + val = local_ns[input] + except KeyError: + val = self.shell.user_ns[input] + self.r.assign(input, self.pyconverter(val)) png_argdict = dict([(n, getattr(args, n)) for n in ['units', 'height', 'width', 'bg', 'pointsize']]) png_args = ','.join(['%s=%s' % (o,v) for o, v in png_argdict.items() if v is not None]) diff --git a/IPython/extensions/tests/test_octavemagic.py b/IPython/extensions/tests/test_octavemagic.py index a94d48f..521570f 100644 --- a/IPython/extensions/tests/test_octavemagic.py +++ b/IPython/extensions/tests/test_octavemagic.py @@ -62,3 +62,24 @@ def test_octave_plot(): 'plot([1, 2, 3]); figure; plot([4, 5, 6]);') nt.assert_equal(test_octave_plot.svgs_generated, 2) + +def test_octavemagic_localscope(): + ip = get_ipython() + ip.magic('load_ext octavemagic') + ip.push({'x':0}) + ip.run_line_magic('octave', '-i x -o result result = x+1') + result = ip.user_ns['result'] + nt.assert_equal(result, 1) + + ip.run_cell('''def octavemagic_addone(u): + %octave -i u -o result result = u+1 + return result''') + ip.run_cell('result = octavemagic_addone(1)') + result = ip.user_ns['result'] + nt.assert_equal(result, 2) + + nt.assert_raises( + KeyError, + ip.run_line_magic, + "octave", + "-i var_not_defined 1+1") diff --git a/IPython/extensions/tests/test_rmagic.py b/IPython/extensions/tests/test_rmagic.py index 5f2d4d4..a5849e9 100644 --- a/IPython/extensions/tests/test_rmagic.py +++ b/IPython/extensions/tests/test_rmagic.py @@ -60,3 +60,23 @@ def test_cell_magic(): ip.run_cell_magic('R', '-i x,y -o r,xc a=lm(y~x)', snippet) np.testing.assert_almost_equal(ip.user_ns['xc'], [3.2, 0.9]) np.testing.assert_almost_equal(ip.user_ns['r'], np.array([-0.2, 0.9, -1. , 0.1, 0.2])) + + +def test_rmagic_localscope(): + ip.push({'x':0}) + ip.run_line_magic('R', '-i x -o result result <-x+1') + result = ip.user_ns['result'] + nt.assert_equal(result[0], 1) + + ip.run_cell('''def rmagic_addone(u): + %R -i u -o result result <- u+1 + return result[0]''') + ip.run_cell('result = rmagic_addone(1)') + result = ip.user_ns['result'] + nt.assert_equal(result, 2) + + nt.assert_raises( + KeyError, + ip.run_line_magic, + "R", + "-i var_not_defined 1+1")