What's the closest I can get to calling a Python function using a different Python version?
<p>Say I have two files:</p>

<pre><code># spam.py
import library_Python3_only as l3

def spam(x,y)
    return l3.bar(x).baz(y)
</code></pre>

<p>and</p>

<pre><code># beans.py
import library_Python2_only as l2

...
</code></pre>

<p>Now suppose I wish to call <code>spam</code> from within <code>beans</code>. It's not directly possible since both files depend on incompatible Python versions. Of course I can <code>Popen</code> a different python process, but how could I pass in the arguments and retrieve the results without too much stream-parsing pain?</p>

<p>Here is a complete example implementation using <code>subprocess</code> and <code>pickle</code> that I actually tested. Note that you need to use protocol version 2 explicitly for pickling on the Python 3 side (at least for the combo Python 3.5.2 and Python 2.7.3).</p>

<pre><code># py3bridge.py

import sys
import pickle
import importlib
import io
import traceback
import subprocess

class Py3Wrapper(object):
    def __init__(self, mod_name, func_name):
        self.mod_name = mod_name
        self.func_name = func_name

    def __call__(self, *args, **kwargs):
        p = subprocess.Popen(['python3', '-m', 'py3bridge',
                              self.mod_name, self.func_name],
                              stdin=subprocess.PIPE,
                              stdout=subprocess.PIPE)
        stdout, _ = p.communicate(pickle.dumps((args, kwargs)))
        data = pickle.loads(stdout)
        if data['success']:
            return data['result']
        else:
            raise Exception(data['stacktrace'])

def main():
    try:
        target_module = sys.argv[1]
        target_function = sys.argv[2]
        args, kwargs = pickle.load(sys.stdin.buffer)
        mod = importlib.import_module(target_module)
        func = getattr(mod, target_function)
        result = func(*args, **kwargs)
        data = dict(success=True, result=result)
    except Exception:
        st = io.StringIO()
        traceback.print_exc(file=st)
        data = dict(success=False, stacktrace=st.getvalue())

    pickle.dump(data, sys.stdout.buffer, 2)

if __name__ == '__main__':
    main()
</code></pre>

<p>The Python 3 module (using the <code>pathlib</code> module for the showcase)</p>

<pre><code># spam.py

import pathlib

def listdir(p):
    return [str(c) for c in pathlib.Path(p).iterdir()]
</code></pre>

<p>The Python 2 module using <code>spam.listdir</code></p>

<pre><code># beans.py

import py3bridge

delegate = py3bridge.Py3Wrapper('spam', 'listdir')
py3result = delegate('.')
print py3result
</code></pre>

python compatibility popen