interpreter.py
754 lines
| 25.7 KiB
| text/x-python
|
PythonLexer
Brian E Granger
|
r1234 | # encoding: utf-8 | ||
"""Central interpreter object for an IPython engine. | ||||
The interpreter is the object whose job is to process lines of user input and | ||||
actually execute them in the user's namespace. | ||||
""" | ||||
__docformat__ = "restructuredtext en" | ||||
#------------------------------------------------------------------------------- | ||||
# Copyright (C) 2008 The IPython Development Team | ||||
# | ||||
# Distributed under the terms of the BSD License. The full license is in | ||||
# the file COPYING, distributed as part of this software. | ||||
#------------------------------------------------------------------------------- | ||||
#------------------------------------------------------------------------------- | ||||
# Imports | ||||
#------------------------------------------------------------------------------- | ||||
# Standard library imports. | ||||
from types import FunctionType | ||||
import __builtin__ | ||||
import codeop | ||||
import compiler | ||||
import sys | ||||
import traceback | ||||
# Local imports. | ||||
from IPython.kernel.core import ultraTB | ||||
from IPython.kernel.core.display_trap import DisplayTrap | ||||
from IPython.kernel.core.macro import Macro | ||||
from IPython.kernel.core.prompts import CachedOutput | ||||
from IPython.kernel.core.traceback_trap import TracebackTrap | ||||
from IPython.kernel.core.util import Bunch, system_shell | ||||
from IPython.external.Itpl import ItplNS | ||||
# Global constants | ||||
COMPILER_ERROR = 'error' | ||||
INCOMPLETE_INPUT = 'incomplete' | ||||
COMPLETE_INPUT = 'complete' | ||||
############################################################################## | ||||
# TEMPORARY!!! fake configuration, while we decide whether to use tconfig or | ||||
# not | ||||
rc = Bunch() | ||||
rc.cache_size = 100 | ||||
rc.pprint = True | ||||
rc.separate_in = '\n' | ||||
rc.separate_out = '\n' | ||||
rc.separate_out2 = '' | ||||
rc.prompt_in1 = r'In [\#]: ' | ||||
rc.prompt_in2 = r' .\\D.: ' | ||||
rc.prompt_out = '' | ||||
rc.prompts_pad_left = False | ||||
############################################################################## | ||||
# Top-level utilities | ||||
def default_display_formatters(): | ||||
""" Return a list of default display formatters. | ||||
""" | ||||
from display_formatter import PPrintDisplayFormatter, ReprDisplayFormatter | ||||
return [PPrintDisplayFormatter(), ReprDisplayFormatter()] | ||||
def default_traceback_formatters(): | ||||
""" Return a list of default traceback formatters. | ||||
""" | ||||
from traceback_formatter import PlainTracebackFormatter | ||||
return [PlainTracebackFormatter()] | ||||
# Top-level classes | ||||
class NotDefined(object): pass | ||||
class Interpreter(object): | ||||
""" An interpreter object. | ||||
fixme: needs to negotiate available formatters with frontends. | ||||
Important: the interpeter should be built so that it exposes a method | ||||
for each attribute/method of its sub-object. This way it can be | ||||
replaced by a network adapter. | ||||
""" | ||||
def __init__(self, user_ns=None, global_ns=None,translator=None, | ||||
magic=None, display_formatters=None, | ||||
traceback_formatters=None, output_trap=None, history=None, | ||||
message_cache=None, filename='<string>', config=None): | ||||
# The local/global namespaces for code execution | ||||
local_ns = user_ns # compatibility name | ||||
if local_ns is None: | ||||
local_ns = {} | ||||
self.user_ns = local_ns | ||||
# The local namespace | ||||
if global_ns is None: | ||||
global_ns = {} | ||||
self.user_global_ns = global_ns | ||||
# An object that will translate commands into executable Python. | ||||
# The current translator does not work properly so for now we are going | ||||
# without! | ||||
# if translator is None: | ||||
# from IPython.kernel.core.translator import IPythonTranslator | ||||
# translator = IPythonTranslator() | ||||
self.translator = translator | ||||
# An object that maintains magic commands. | ||||
if magic is None: | ||||
from IPython.kernel.core.magic import Magic | ||||
magic = Magic(self) | ||||
self.magic = magic | ||||
# A list of formatters for the displayhook. | ||||
if display_formatters is None: | ||||
display_formatters = default_display_formatters() | ||||
self.display_formatters = display_formatters | ||||
# A list of formatters for tracebacks. | ||||
if traceback_formatters is None: | ||||
traceback_formatters = default_traceback_formatters() | ||||
self.traceback_formatters = traceback_formatters | ||||
# The object trapping stdout/stderr. | ||||
if output_trap is None: | ||||
from IPython.kernel.core.output_trap import OutputTrap | ||||
output_trap = OutputTrap() | ||||
self.output_trap = output_trap | ||||
# An object that manages the history. | ||||
if history is None: | ||||
from IPython.kernel.core.history import InterpreterHistory | ||||
history = InterpreterHistory() | ||||
self.history = history | ||||
self.get_history_item = history.get_history_item | ||||
self.get_history_input_cache = history.get_input_cache | ||||
self.get_history_input_after = history.get_input_after | ||||
# An object that caches all of the return messages. | ||||
if message_cache is None: | ||||
from IPython.kernel.core.message_cache import SimpleMessageCache | ||||
message_cache = SimpleMessageCache() | ||||
self.message_cache = message_cache | ||||
# The "filename" of the code that is executed in this interpreter. | ||||
self.filename = filename | ||||
# An object that contains much configuration information. | ||||
if config is None: | ||||
# fixme: Move this constant elsewhere! | ||||
config = Bunch(ESC_MAGIC='%') | ||||
self.config = config | ||||
# Hook managers. | ||||
# fixme: make the display callbacks configurable. In the meantime, | ||||
# enable macros. | ||||
self.display_trap = DisplayTrap( | ||||
formatters=self.display_formatters, | ||||
callbacks=[self._possible_macro], | ||||
) | ||||
self.traceback_trap = TracebackTrap( | ||||
formatters=self.traceback_formatters) | ||||
# This is used temporarily for reformating exceptions in certain | ||||
# cases. It will go away once the ultraTB stuff is ported | ||||
# to ipython1 | ||||
self.tbHandler = ultraTB.FormattedTB(color_scheme='NoColor', | ||||
mode='Context', | ||||
tb_offset=2) | ||||
# An object that can compile commands and remember __future__ | ||||
# statements. | ||||
self.command_compiler = codeop.CommandCompiler() | ||||
# A replacement for the raw_input() and input() builtins. Change these | ||||
# attributes later to configure them. | ||||
self.raw_input_builtin = raw_input | ||||
self.input_builtin = input | ||||
# The number of the current cell. | ||||
self.current_cell_number = 1 | ||||
# Initialize cache, set in/out prompts and printing system | ||||
self.outputcache = CachedOutput(self, | ||||
rc.cache_size, | ||||
rc.pprint, | ||||
input_sep = rc.separate_in, | ||||
output_sep = rc.separate_out, | ||||
output_sep2 = rc.separate_out2, | ||||
ps1 = rc.prompt_in1, | ||||
ps2 = rc.prompt_in2, | ||||
ps_out = rc.prompt_out, | ||||
pad_left = rc.prompts_pad_left) | ||||
# Need to decide later if this is the right approach, but clients | ||||
# commonly use sys.ps1/2, so it may be best to just set them here | ||||
sys.ps1 = self.outputcache.prompt1.p_str | ||||
sys.ps2 = self.outputcache.prompt2.p_str | ||||
# This is the message dictionary assigned temporarily when running the | ||||
# code. | ||||
self.message = None | ||||
self.setup_namespace() | ||||
#### Public 'Interpreter' interface ######################################## | ||||
def formatTraceback(self, et, ev, tb, message=''): | ||||
"""Put a formatted version of the traceback into value and reraise. | ||||
When exceptions have to be sent over the network, the traceback | ||||
needs to be put into the value of the exception in a nicely | ||||
formatted way. The method takes the type, value and tb of an | ||||
exception and puts a string representation of the tb into the | ||||
value of the exception and reraises it. | ||||
Currently this method uses the ultraTb formatter from IPython trunk. | ||||
Eventually it should simply use the traceback formatters in core | ||||
that are loaded into self.tracback_trap.formatters. | ||||
""" | ||||
tbinfo = self.tbHandler.text(et,ev,tb) | ||||
ev._ipython_traceback_text = tbinfo | ||||
return et, ev, tb | ||||
def execute(self, commands, raiseException=True): | ||||
""" Execute some IPython commands. | ||||
1. Translate them into Python. | ||||
2. Run them. | ||||
3. Trap stdout/stderr. | ||||
4. Trap sys.displayhook(). | ||||
5. Trap exceptions. | ||||
6. Return a message object. | ||||
Parameters | ||||
---------- | ||||
commands : str | ||||
The raw commands that the user typed into the prompt. | ||||
Returns | ||||
------- | ||||
message : dict | ||||
The dictionary of responses. See the README.txt in this directory | ||||
for an explanation of the format. | ||||
""" | ||||
# Create a message dictionary with all of the information we will be | ||||
# returning to the frontend and other listeners. | ||||
message = self.setup_message() | ||||
# Massage the input and store the raw and translated commands into | ||||
# a dict. | ||||
user_input = dict(raw=commands) | ||||
if self.translator is not None: | ||||
python = self.translator(commands, message) | ||||
if python is None: | ||||
# Something went wrong with the translation. The translator | ||||
# should have added an appropriate entry to the message object. | ||||
return message | ||||
else: | ||||
python = commands | ||||
user_input['translated'] = python | ||||
message['input'] = user_input | ||||
# Set the message object so that any magics executed in the code have | ||||
# access. | ||||
self.message = message | ||||
# Set all of the output/exception traps. | ||||
self.set_traps() | ||||
# Actually execute the Python code. | ||||
status = self.execute_python(python) | ||||
# Unset all of the traps. | ||||
self.unset_traps() | ||||
# Unset the message object. | ||||
self.message = None | ||||
# Update the history variables in the namespace. | ||||
# E.g. In, Out, _, __, ___ | ||||
if self.history is not None: | ||||
self.history.update_history(self, python) | ||||
# Let all of the traps contribute to the message and then clear their | ||||
# stored information. | ||||
self.output_trap.add_to_message(message) | ||||
self.output_trap.clear() | ||||
self.display_trap.add_to_message(message) | ||||
self.display_trap.clear() | ||||
self.traceback_trap.add_to_message(message) | ||||
# Pull out the type, value and tb of the current exception | ||||
# before clearing it. | ||||
einfo = self.traceback_trap.args | ||||
self.traceback_trap.clear() | ||||
# Cache the message. | ||||
self.message_cache.add_message(self.current_cell_number, message) | ||||
# Bump the number. | ||||
self.current_cell_number += 1 | ||||
# This conditional lets the execute method either raise any | ||||
# exception that has occured in user code OR return the message | ||||
# dict containing the traceback and other useful info. | ||||
if raiseException and einfo: | ||||
raise einfo[0],einfo[1],einfo[2] | ||||
else: | ||||
return message | ||||
def generate_prompt(self, is_continuation): | ||||
"""Calculate and return a string with the prompt to display. | ||||
:Parameters: | ||||
is_continuation : bool | ||||
Whether the input line is continuing multiline input or not, so | ||||
that a proper continuation prompt can be computed.""" | ||||
if is_continuation: | ||||
return str(self.outputcache.prompt2) | ||||
else: | ||||
return str(self.outputcache.prompt1) | ||||
def execute_python(self, python): | ||||
""" Actually run the Python code in the namespace. | ||||
:Parameters: | ||||
python : str | ||||
Pure, exec'able Python code. Special IPython commands should have | ||||
already been translated into pure Python. | ||||
""" | ||||
# We use a CommandCompiler instance to compile the code so as to keep | ||||
# track of __future__ imports. | ||||
try: | ||||
commands = self.split_commands(python) | ||||
except (SyntaxError, IndentationError), e: | ||||
# Save the exc_info so compilation related exceptions can be | ||||
# reraised | ||||
self.traceback_trap.args = sys.exc_info() | ||||
self.pack_exception(self.message,e) | ||||
return None | ||||
for cmd in commands: | ||||
try: | ||||
code = self.command_compiler(cmd, self.filename, 'single') | ||||
except (SyntaxError, OverflowError, ValueError), e: | ||||
self.traceback_trap.args = sys.exc_info() | ||||
self.pack_exception(self.message,e) | ||||
# No point in continuing if one block raised | ||||
return None | ||||
else: | ||||
self.execute_block(code) | ||||
def execute_block(self,code): | ||||
"""Execute a single block of code in the user namespace. | ||||
Return value: a flag indicating whether the code to be run completed | ||||
successfully: | ||||
- 0: successful execution. | ||||
- 1: an error occurred. | ||||
""" | ||||
outflag = 1 # start by assuming error, success will reset it | ||||
try: | ||||
exec code in self.user_ns | ||||
outflag = 0 | ||||
except SystemExit: | ||||
self.resetbuffer() | ||||
self.traceback_trap.args = sys.exc_info() | ||||
except: | ||||
self.traceback_trap.args = sys.exc_info() | ||||
return outflag | ||||
def execute_macro(self, macro): | ||||
""" Execute the value of a macro. | ||||
Parameters | ||||
---------- | ||||
macro : Macro | ||||
""" | ||||
python = macro.value | ||||
if self.translator is not None: | ||||
python = self.translator(python) | ||||
self.execute_python(python) | ||||
def getCommand(self, i=None): | ||||
"""Gets the ith message in the message_cache. | ||||
This is implemented here for compatibility with the old ipython1 shell | ||||
I am not sure we need this though. I even seem to remember that we | ||||
were going to get rid of it. | ||||
""" | ||||
return self.message_cache.get_message(i) | ||||
def reset(self): | ||||
"""Reset the interpreter. | ||||
Currently this only resets the users variables in the namespace. | ||||
In the future we might want to also reset the other stateful | ||||
things like that the Interpreter has, like In, Out, etc. | ||||
""" | ||||
self.user_ns.clear() | ||||
self.setup_namespace() | ||||
def complete(self,line,text=None, pos=None): | ||||
"""Complete the given text. | ||||
:Parameters: | ||||
text : str | ||||
Text fragment to be completed on. Typically this is | ||||
""" | ||||
# fixme: implement | ||||
raise NotImplementedError | ||||
def push(self, ns): | ||||
""" Put value into the namespace with name key. | ||||
Parameters | ||||
---------- | ||||
**kwds | ||||
""" | ||||
self.user_ns.update(ns) | ||||
def push_function(self, ns): | ||||
# First set the func_globals for all functions to self.user_ns | ||||
new_kwds = {} | ||||
for k, v in ns.iteritems(): | ||||
if not isinstance(v, FunctionType): | ||||
raise TypeError("function object expected") | ||||
new_kwds[k] = FunctionType(v.func_code, self.user_ns) | ||||
self.user_ns.update(new_kwds) | ||||
def pack_exception(self,message,exc): | ||||
message['exception'] = exc.__class__ | ||||
message['exception_value'] = \ | ||||
traceback.format_exception_only(exc.__class__, exc) | ||||
def feed_block(self, source, filename='<input>', symbol='single'): | ||||
"""Compile some source in the interpreter. | ||||
One several things can happen: | ||||
1) The input is incorrect; compile_command() raised an | ||||
exception (SyntaxError or OverflowError). | ||||
2) The input is incomplete, and more input is required; | ||||
compile_command() returned None. Nothing happens. | ||||
3) The input is complete; compile_command() returned a code | ||||
object. The code is executed by calling self.runcode() (which | ||||
also handles run-time exceptions, except for SystemExit). | ||||
The return value is: | ||||
- True in case 2 | ||||
- False in the other cases, unless an exception is raised, where | ||||
None is returned instead. This can be used by external callers to | ||||
know whether to continue feeding input or not. | ||||
The return value can be used to decide whether to use sys.ps1 or | ||||
sys.ps2 to prompt the next line.""" | ||||
self.message = self.setup_message() | ||||
try: | ||||
code = self.command_compiler(source,filename,symbol) | ||||
except (OverflowError, SyntaxError, IndentationError, ValueError ), e: | ||||
# Case 1 | ||||
self.traceback_trap.args = sys.exc_info() | ||||
self.pack_exception(self.message,e) | ||||
return COMPILER_ERROR,False | ||||
if code is None: | ||||
# Case 2: incomplete input. This means that the input can span | ||||
# multiple lines. But we still need to decide when to actually | ||||
# stop taking user input. Later we'll add auto-indentation support | ||||
# somehow. In the meantime, we'll just stop if there are two lines | ||||
# of pure whitespace at the end. | ||||
last_two = source.rsplit('\n',2)[-2:] | ||||
print 'last two:',last_two # dbg | ||||
if len(last_two)==2 and all(s.isspace() for s in last_two): | ||||
return COMPLETE_INPUT,False | ||||
else: | ||||
return INCOMPLETE_INPUT, True | ||||
else: | ||||
# Case 3 | ||||
return COMPLETE_INPUT, False | ||||
def pull(self, keys): | ||||
""" Get an item out of the namespace by key. | ||||
Parameters | ||||
---------- | ||||
key : str | ||||
Returns | ||||
------- | ||||
value : object | ||||
Raises | ||||
------ | ||||
TypeError if the key is not a string. | ||||
NameError if the object doesn't exist. | ||||
""" | ||||
if isinstance(keys, str): | ||||
result = self.user_ns.get(keys, NotDefined()) | ||||
if isinstance(result, NotDefined): | ||||
raise NameError('name %s is not defined' % keys) | ||||
elif isinstance(keys, (list, tuple)): | ||||
result = [] | ||||
for key in keys: | ||||
if not isinstance(key, str): | ||||
raise TypeError("objects must be keyed by strings.") | ||||
else: | ||||
r = self.user_ns.get(key, NotDefined()) | ||||
if isinstance(r, NotDefined): | ||||
raise NameError('name %s is not defined' % key) | ||||
else: | ||||
result.append(r) | ||||
if len(keys)==1: | ||||
result = result[0] | ||||
else: | ||||
raise TypeError("keys must be a strong or a list/tuple of strings") | ||||
return result | ||||
def pull_function(self, keys): | ||||
return self.pull(keys) | ||||
#### Interactive user API ################################################## | ||||
def ipsystem(self, command): | ||||
""" Execute a command in a system shell while expanding variables in the | ||||
current namespace. | ||||
Parameters | ||||
---------- | ||||
command : str | ||||
""" | ||||
# Expand $variables. | ||||
command = self.var_expand(command) | ||||
system_shell(command, | ||||
header='IPython system call: ', | ||||
verbose=self.rc.system_verbose, | ||||
) | ||||
def ipmagic(self, arg_string): | ||||
""" Call a magic function by name. | ||||
ipmagic('name -opt foo bar') is equivalent to typing at the ipython | ||||
prompt: | ||||
In[1]: %name -opt foo bar | ||||
To call a magic without arguments, simply use ipmagic('name'). | ||||
This provides a proper Python function to call IPython's magics in any | ||||
valid Python code you can type at the interpreter, including loops and | ||||
compound statements. It is added by IPython to the Python builtin | ||||
namespace upon initialization. | ||||
Parameters | ||||
---------- | ||||
arg_string : str | ||||
A string containing the name of the magic function to call and any | ||||
additional arguments to be passed to the magic. | ||||
Returns | ||||
------- | ||||
something : object | ||||
The return value of the actual object. | ||||
""" | ||||
# Taken from IPython. | ||||
raise NotImplementedError('Not ported yet') | ||||
args = arg_string.split(' ', 1) | ||||
magic_name = args[0] | ||||
magic_name = magic_name.lstrip(self.config.ESC_MAGIC) | ||||
try: | ||||
magic_args = args[1] | ||||
except IndexError: | ||||
magic_args = '' | ||||
fn = getattr(self.magic, 'magic_'+magic_name, None) | ||||
if fn is None: | ||||
self.error("Magic function `%s` not found." % magic_name) | ||||
else: | ||||
magic_args = self.var_expand(magic_args) | ||||
return fn(magic_args) | ||||
#### Private 'Interpreter' interface ####################################### | ||||
def setup_message(self): | ||||
"""Return a message object. | ||||
This method prepares and returns a message dictionary. This dict | ||||
contains the various fields that are used to transfer information about | ||||
execution, results, tracebacks, etc, to clients (either in or out of | ||||
process ones). Because of the need to work with possibly out of | ||||
process clients, this dict MUST contain strictly pickle-safe values. | ||||
""" | ||||
return dict(number=self.current_cell_number) | ||||
def setup_namespace(self): | ||||
""" Add things to the namespace. | ||||
""" | ||||
self.user_ns.setdefault('__name__', '__main__') | ||||
self.user_ns.setdefault('__builtins__', __builtin__) | ||||
self.user_ns['__IP'] = self | ||||
if self.raw_input_builtin is not None: | ||||
self.user_ns['raw_input'] = self.raw_input_builtin | ||||
if self.input_builtin is not None: | ||||
self.user_ns['input'] = self.input_builtin | ||||
builtin_additions = dict( | ||||
ipmagic=self.ipmagic, | ||||
) | ||||
__builtin__.__dict__.update(builtin_additions) | ||||
if self.history is not None: | ||||
self.history.setup_namespace(self.user_ns) | ||||
def set_traps(self): | ||||
""" Set all of the output, display, and traceback traps. | ||||
""" | ||||
self.output_trap.set() | ||||
self.display_trap.set() | ||||
self.traceback_trap.set() | ||||
def unset_traps(self): | ||||
""" Unset all of the output, display, and traceback traps. | ||||
""" | ||||
self.output_trap.unset() | ||||
self.display_trap.unset() | ||||
self.traceback_trap.unset() | ||||
def split_commands(self, python): | ||||
""" Split multiple lines of code into discrete commands that can be | ||||
executed singly. | ||||
Parameters | ||||
---------- | ||||
python : str | ||||
Pure, exec'able Python code. | ||||
Returns | ||||
------- | ||||
commands : list of str | ||||
Separate commands that can be exec'ed independently. | ||||
""" | ||||
# compiler.parse treats trailing spaces after a newline as a | ||||
# SyntaxError. This is different than codeop.CommandCompiler, which | ||||
# will compile the trailng spaces just fine. We simply strip any | ||||
# trailing whitespace off. Passing a string with trailing whitespace | ||||
# to exec will fail however. There seems to be some inconsistency in | ||||
# how trailing whitespace is handled, but this seems to work. | ||||
python = python.strip() | ||||
gvaroquaux
|
r1642 | # The compiler module does not like unicode. We need to convert | ||
# it encode it: | ||||
if isinstance(python, unicode): | ||||
# Use the utf-8-sig BOM so the compiler detects this a UTF-8 | ||||
# encode string. | ||||
python = '\xef\xbb\xbf' + python.encode('utf-8') | ||||
Brian E Granger
|
r1234 | # The compiler module will parse the code into an abstract syntax tree. | ||
ast = compiler.parse(python) | ||||
# Uncomment to help debug the ast tree | ||||
# for n in ast.node: | ||||
# print n.lineno,'->',n | ||||
# Each separate command is available by iterating over ast.node. The | ||||
# lineno attribute is the line number (1-indexed) beginning the commands | ||||
# suite. | ||||
# lines ending with ";" yield a Discard Node that doesn't have a lineno | ||||
# attribute. These nodes can and should be discarded. But there are | ||||
# other situations that cause Discard nodes that shouldn't be discarded. | ||||
# We might eventually discover other cases where lineno is None and have | ||||
# to put in a more sophisticated test. | ||||
linenos = [x.lineno-1 for x in ast.node if x.lineno is not None] | ||||
# When we finally get the slices, we will need to slice all the way to | ||||
# the end even though we don't have a line number for it. Fortunately, | ||||
# None does the job nicely. | ||||
linenos.append(None) | ||||
lines = python.splitlines() | ||||
# Create a list of atomic commands. | ||||
cmds = [] | ||||
for i, j in zip(linenos[:-1], linenos[1:]): | ||||
cmd = lines[i:j] | ||||
if cmd: | ||||
cmds.append('\n'.join(cmd)+'\n') | ||||
return cmds | ||||
def error(self, text): | ||||
""" Pass an error message back to the shell. | ||||
Preconditions | ||||
------------- | ||||
This should only be called when self.message is set. In other words, | ||||
when code is being executed. | ||||
Parameters | ||||
---------- | ||||
text : str | ||||
""" | ||||
errors = self.message.get('IPYTHON_ERROR', []) | ||||
errors.append(text) | ||||
def var_expand(self, template): | ||||
""" Expand $variables in the current namespace using Itpl. | ||||
Parameters | ||||
---------- | ||||
template : str | ||||
""" | ||||
return str(ItplNS(template, self.user_ns)) | ||||
def _possible_macro(self, obj): | ||||
""" If the object is a macro, execute it. | ||||
""" | ||||
if isinstance(obj, Macro): | ||||
self.execute_macro(obj) | ||||