It is possible, but the solution may depend on the implementation of Python.
For example, in Python 2.7 the module can inspectalso sys.settrace()be used to change locals()for a specific frame. This can also be done in the Python / C API, but Python frame management in Python is often much more manageable.
example.py update_locals() locals() :
import inspect
import sys
def _force_locals(old_frame, new_locals):
''' Force a new set of locals on a given frame.
:param old_frame: The frame to which locals will be applied.
:type old_frame: frame.
:param new_locals: What locals() should return for old_frame.
:type new_locals: dict.
.. note:: This function will force a custom trace function onto
the old_frame. Within the context of a trace function
(often used for debugging), a frame f_locals is
modifiable. In most execution paths, f_locals is
writable but not modifiable.
'''
if not sys.gettrace():
sys.settrace(lambda *args, **keys: None)
def trace(frame, event, arg):
frame.f_locals.update(new_locals)
del frame.f_trace
old_frame.f_trace = trace
def update_locals(frame, *refs):
''' Modifies a frame locals based on the provided references.
:param frame: The frame from which a locals will be updated.
:type frame: frame.
:param refs: Iterable pair of (old_ref, new_ref) tuples.
:type refs: Iterable type of pairs.
'''
new_locals = frame.f_locals.copy()
has_changes = False
for key, ref in new_locals.iteritems():
for old_ref, new_ref in refs:
if ref is old_ref:
new_locals[key] = new_ref
has_changes = True
if has_changes:
_force_locals(frame, new_locals)
:
>>> import example
>>> import inspect
>>> x = 42
>>> x
42
>>> example.update_locals(inspect.currentframe(), (x, '3.14'))
>>> x
'3.14'
x int(42), example.update_locals() x str('3.14') .
++ fun() Python, A, str, ++ fun(A&) .
++ Spam fun(Spam&) _example Python.
#include <iostream>
#include <string>
#include <boost/python.hpp>
class Spam
{
public:
explicit Spam(std::string str)
: str_(str)
{}
void action()
{
std::cout << "Spam::action(): " << str_ << std::endl;
}
private:
std::string str_;
};
void fun(Spam& spam)
{
std::cout << "fun() -> ";
spam.action();
}
BOOST_PYTHON_MODULE(_example)
{
namespace python = boost::python;
python::class_<Spam>("Spam", python::init<std::string>());
python::def("fun", &fun);
}
example _example.fun(), Spam, , fun(), str ,
from _example import *
import inspect
import sys
def _force_locals(old_frame, new_locals):
''' Force a new set of locals on a given frame.
:param old_frame: The frame to which locals will be applied.
:type old_frame: frame.
:param new_locals: What locals() should return for old_frame.
:type new_locals: dict.
.. note:: This function will force a custom trace function onto
the old_frame. Within the context of a trace function
(often used for debugging), a frame f_locals is
modifiable. In most execution paths, f_locals is
writable but not modifiable.
'''
if not sys.gettrace():
sys.settrace(lambda *args, **keys: None)
def trace(frame, event, arg):
frame.f_locals.update(new_locals)
del frame.f_trace
old_frame.f_trace = trace
def _update_locals(frame, *refs):
''' Modifies a frame locals based on the provided references.
:param frame: The frame from which a locals will be updated.
:type frame: frame.
:param refs: Iterable pair of (old_ref, new_ref) tuples.
:type refs: Iterable type of pairs.
'''
new_locals = frame.f_locals.copy()
has_changes = False
for key, ref in new_locals.iteritems():
for old_ref, new_ref in refs:
if ref is old_ref:
new_locals[key] = new_ref
has_changes = True
if has_changes:
_force_locals(frame, new_locals)
def _patch_fun():
old_fun = fun
def patch(spam, *args, **kwargs):
if isinstance(spam, str):
old_spam, spam = spam, Spam(spam)
_update_locals(
inspect.currentframe(1),
(old_spam, spam))
return old_fun(spam, *args, **kwargs)
return patch
fun = _patch_fun()
:
>>> import example
>>> s1 = example.Spam('abc')
>>> type(s1)
<class '_example.Spam'>
>>> example.fun(s1)
fun() -> Spam::action(): abc
>>> type(s1)
<class '_example.Spam'>
>>> s2 = 'def'
>>> type(s2)
<type 'str'>
>>> example.fun(s2)
fun() -> Spam::action(): def
>>> type(s2) # s2 type has changed.
<class '_example.Spam'>
>>> example.fun('ghi')
fun() -> Spam::action(): ghi
, fun() , .