Boost.python automatically convert parameter

后端 未结 2 1990
离开以前
离开以前 2021-01-20 17:16

I am using boost.python to wrap a C++ class \'A\' which takes a string as constructor. I then have a function \'fun(A& arg)\' which takes a reference to an \'A\' as para

相关标签:
2条回答
  • 2021-01-20 17:36

    This is possible, but the solution may be dependent on the Python implementation.

    For example, in Python 2.7, the inspect module and sys.settrace() can be used to modify locals() on a specific frame. This can also be accomplished in the Python/C API, but it is often far more manageable to manipulate Python frames within Python.

    In the below example.py, the update_locals() function will update the locals() in a given frame:

    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's f_locals is
                  modifiable.  In most execution paths, f_locals is 
                  writable but not modifiable.
    
        '''
        # Force tracing by setting the global tracing function to
        # any non-None function.  
        if not sys.gettrace():
            sys.settrace(lambda *args, **keys: None)
    
        # Custom trace function that will force locals.
        def trace(frame, event, arg):
            # Update the frame's locals.
            frame.f_locals.update(new_locals)
            # Set frame to use default trace, preventing this trace
            # function from resetting the locals on each trace call.
            del frame.f_trace
    
        # Set the old frame's trace function to the custom trace.
        old_frame.f_trace = trace
    
    
    def update_locals(frame, *refs):
        ''' Modifies a frame's 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
    
        # If a frame's local has an identity patch with a provided
        # reference, then update new_locals with the new reference.
        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 new_locals has changes, then force them onto the frame.
        if has_changes:
            _force_locals(frame, new_locals)
    

    Interactive usage:

    >>> import example
    >>> import inspect
    >>> x = 42
    >>> x
    42
    >>> example.update_locals(inspect.currentframe(), (x, '3.14'))
    >>> x
    '3.14'
    

    The x variable referenced the int(42) object, but the example.update_locals() function changed x to reference str('3.14') object.


    With being able to modify the caller's frame, the next step is to monkey patch the C++ fun() in Python to construct an instance of A if the argument is an instance of str, then delegate to the C++ fun(A&) function and update the caller's frame.

    In example below, a C++ Spam type and fun(Spam&) function are exposed to the _example Python module.

    #include <iostream>
    #include <string>
    #include <boost/python.hpp>
    
    /// @brief Mockup type.
    class Spam
    {
    public:
      explicit Spam(std::string str)
        : str_(str)
      {}
    
      void action()
      {
        std::cout << "Spam::action(): " << str_ << std::endl;
      }
    
    private:
      std::string str_;
    };
    
    /// @brief Mockup function.
    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);
    }
    

    A higher level example module will monkey patch _example.fun() to construct a Spam object if the argument provided to fun() is an instance of str and manipulate the caller's frame in a similar manner as demonstrated above:

    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's f_locals is
                  modifiable.  In most execution paths, f_locals is 
                  writable but not modifiable.
    
        '''
        # Force tracing by setting the global tracing function to
        # any non-None function.  
        if not sys.gettrace():
            sys.settrace(lambda *args, **keys: None)
    
        # Custom trace function that will force locals.
        def trace(frame, event, arg):
            # Update the frame's locals.
            frame.f_locals.update(new_locals)
            # Set frame to use default trace, preventing this trace
            # function from resetting the locals on each trace call.
            del frame.f_trace
    
        # Set the old frame's trace function to the custom trace.
        old_frame.f_trace = trace
    
    
    def _update_locals(frame, *refs):
        ''' Modifies a frame's 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
    
        # If a frame's local has an identity patch with a provided
        # reference, then update new_locals with the new reference.
        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 new_locals has changes, then force them onto the frame.
        if has_changes:
            _force_locals(frame, new_locals)
    
    
    def _patch_fun():
        old_fun = fun
        # Create a function that will perform custom operations then
        # delegate to the original function.
        def patch(spam, *args, **kwargs):
            if isinstance(spam, str):
                old_spam, spam = spam, Spam(spam)
    
                # In the caller's frame, force the variables that reference
                # old_spam to now reference spam.
                _update_locals(
                    inspect.currentframe(1), # Caller's frame.
                    (old_spam, spam))
    
            return old_fun(spam, *args, **kwargs)
        return patch
    
    fun = _patch_fun()
    

    Interactive usage:

    >>> import example
    >>> s1 = example.Spam('abc')
    >>> type(s1)
    <class '_example.Spam'>
    >>> example.fun(s1)
    fun() -> Spam::action(): abc
    >>> type(s1) # s1's type has not changed.
    <class '_example.Spam'>
    >>> s2 = 'def'
    >>> type(s2)
    <type 'str'>
    >>> example.fun(s2)
    fun() -> Spam::action(): def
    >>> type(s2) # s2's type has changed.
    <class '_example.Spam'>
    >>> example.fun('ghi')
    fun() -> Spam::action(): ghi
    

    Note that the frame manipulation in the above example only modifies's fun()'s caller's frame and not the entire stack.

    0 讨论(0)
  • 2021-01-20 17:59

    For that to work fun(a) would have to modify the original a object reference. What fun(a) actually gets is a local copy of object reference a, not the original a object reference passed as an argument.

    In other words, Python does not work like that, you would need to call it as a = fun(a) to be able to change reference a (not the object it refers to).

    0 讨论(0)
提交回复
热议问题