From fe31209e3914a63106b040d9a6fb3f97e86a3040 Mon Sep 17 00:00:00 2001 From: Jeremy Howard Date: Sat, 26 Oct 2024 06:11:36 +1000 Subject: [PATCH] fixes #643 --- fastcore/_modidx.py | 1 + fastcore/xtras.py | 30 ++++++++++--- nbs/03_xtras.ipynb | 104 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 130 insertions(+), 5 deletions(-) diff --git a/fastcore/_modidx.py b/fastcore/_modidx.py index d78aa9b8..79773e7c 100644 --- a/fastcore/_modidx.py +++ b/fastcore/_modidx.py @@ -686,6 +686,7 @@ 'fastcore.xtras.dataclass_src': ('xtras.html#dataclass_src', 'fastcore/xtras.py'), 'fastcore.xtras.dict2obj': ('xtras.html#dict2obj', 'fastcore/xtras.py'), 'fastcore.xtras.dumps': ('xtras.html#dumps', 'fastcore/xtras.py'), + 'fastcore.xtras.exec_eval': ('xtras.html#exec_eval', 'fastcore/xtras.py'), 'fastcore.xtras.expand_wildcards': ('xtras.html#expand_wildcards', 'fastcore/xtras.py'), 'fastcore.xtras.flexicache': ('xtras.html#flexicache', 'fastcore/xtras.py'), 'fastcore.xtras.flexiclass': ('xtras.html#flexiclass', 'fastcore/xtras.py'), diff --git a/fastcore/xtras.py b/fastcore/xtras.py index 40111d2f..cfefb052 100644 --- a/fastcore/xtras.py +++ b/fastcore/xtras.py @@ -9,11 +9,12 @@ __all__ = ['spark_chars', 'UNSET', 'walk', 'globtastic', 'maybe_open', 'mkdir', 'image_size', 'bunzip', 'loads', 'loads_multi', 'dumps', 'untar_dir', 'repo_details', 'run', 'open_file', 'save_pickle', 'load_pickle', 'parse_env', 'expand_wildcards', 'dict2obj', 'obj2dict', 'repr_dict', 'is_listy', 'mapped', 'IterLen', - 'ReindexCollection', 'get_source_link', 'truncstr', 'sparkline', 'modify_exception', 'round_multiple', - 'set_num_threads', 'join_path_file', 'autostart', 'EventTimer', 'stringfmt_names', 'PartialFormatter', - 'partial_format', 'utc2local', 'local2utc', 'trace', 'modified_env', 'ContextManagers', 'shufflish', - 'console_help', 'hl_md', 'type2str', 'dataclass_src', 'Unset', 'nullable_dc', 'make_nullable', 'flexiclass', - 'asdict', 'is_typeddict', 'is_namedtuple', 'flexicache', 'time_policy', 'mtime_policy', 'timed_cache'] + 'ReindexCollection', 'exec_eval', 'get_source_link', 'truncstr', 'sparkline', 'modify_exception', + 'round_multiple', 'set_num_threads', 'join_path_file', 'autostart', 'EventTimer', 'stringfmt_names', + 'PartialFormatter', 'partial_format', 'utc2local', 'local2utc', 'trace', 'modified_env', 'ContextManagers', + 'shufflish', 'console_help', 'hl_md', 'type2str', 'dataclass_src', 'Unset', 'nullable_dc', 'make_nullable', + 'flexiclass', 'asdict', 'is_typeddict', 'is_namedtuple', 'flexicache', 'time_policy', 'mtime_policy', + 'timed_cache'] # %% ../nbs/03_xtras.ipynb from .imports import * @@ -408,6 +409,25 @@ def __setstate__(self, s): self.coll,self.idxs,self.cache,self.tfm = s['coll'],s shuffle="Randomly shuffle indices", cache_clear="Clear LRU cache") +# %% ../nbs/03_xtras.ipynb +def exec_eval(code, # Code to exec/eval + g=None, # Globals namespace dict + l=None # Locals namespace dict + ): + "Evaluate `code` in `g` (defaults to `globals()`) and `l` (defaults to `locals()`)" + import ast, inspect + frame = inspect.currentframe().f_back + if l is None: l = g if g else frame.f_locals + if g is None: g = frame.f_globals + tree = ast.parse(code, mode='exec') + if tree.body and isinstance(tree.body[-1], ast.Expr): + *statements, expr = tree.body + exec_code = compile(ast.Module(statements, []), filename="", mode="exec") + expr_code = compile(ast.Expression(expr.value), filename="", mode="eval") + exec(exec_code, g, l) + return eval(expr_code, g, l) + else: exec(code, g, l) + # %% ../nbs/03_xtras.ipynb def _is_type_dispatch(x): return type(x).__name__ == "TypeDispatch" def _unwrapped_type_dispatch_func(x): return x.first() if _is_type_dispatch(x) else x diff --git a/nbs/03_xtras.ipynb b/nbs/03_xtras.ipynb index d11a3b83..ed1d042b 100644 --- a/nbs/03_xtras.ipynb +++ b/nbs/03_xtras.ipynb @@ -1785,6 +1785,110 @@ "## Other Helpers" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#| export\n", + "def exec_eval(code, # Code to exec/eval\n", + " g=None, # Globals namespace dict\n", + " l=None # Locals namespace dict\n", + " ):\n", + " \"Evaluate `code` in `g` (defaults to `globals()`) and `l` (defaults to `locals()`)\"\n", + " import ast, inspect\n", + " frame = inspect.currentframe().f_back\n", + " if l is None: l = g if g else frame.f_locals\n", + " if g is None: g = frame.f_globals\n", + " tree = ast.parse(code, mode='exec')\n", + " if tree.body and isinstance(tree.body[-1], ast.Expr):\n", + " *statements, expr = tree.body\n", + " exec_code = compile(ast.Module(statements, []), filename=\"\", mode=\"exec\")\n", + " expr_code = compile(ast.Expression(expr.value), filename=\"\", mode=\"eval\")\n", + " exec(exec_code, g, l)\n", + " return eval(expr_code, g, l)\n", + " else: exec(code, g, l)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This is a combination of `eval` and `exec`, which behaves like ipython and Jupyter. If the last line is an expression, it is evaluated and the result is returned:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "2" + ] + }, + "execution_count": null, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "exec_eval('''\n", + "def f(x): return x+1\n", + "f(1)\n", + "''')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "By default, the code uses the caller's globals and locals. For instance, here `f` is available since it's been added to our symbol table:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "3\n" + ] + } + ], + "source": [ + "exec_eval('print(f(2))')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Pass a dict as the `g` param in order to use an arbitrary namespace:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Hi I am f.\n" + ] + } + ], + "source": [ + "exec_eval('print(f)', {'f': 'Hi I am f.'})" + ] + }, { "cell_type": "code", "execution_count": null,