使用不同的Python版本调用Python函数最接近的是什么?

时间:2016-09-12 13:45:21

标签: python compatibility popen

说我有两个文件:

# spam.py
import library_Python3_only as l3

def spam(x,y)
    return l3.bar(x).baz(y)

# beans.py
import library_Python2_only as l2

...

现在假设我想从spam内拨打beans。它不是直接可能的,因为两个文件都依赖于不兼容的Python版本。当然我可以Popen一个不同的python进程,但是我怎么能传入参数并检索结果而没有太多的流分析痛苦?

4 个答案:

答案 0 :(得分:12)

以下是使用我实际测试的subprocesspickle的完整示例实现。请注意,您需要在Python 3端明确使用协议版本2(至少对于组合Python 3.5.2和Python 2.7.3)。

# 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()

Python 3模块(使用pathlib模块进行展示)

# spam.py

import pathlib

def listdir(p):
    return [str(c) for c in pathlib.Path(p).iterdir()]

使用spam.listdir

的Python 2模块
# beans.py

import py3bridge

delegate = py3bridge.Py3Wrapper('spam', 'listdir')
py3result = delegate('.')
print py3result

答案 1 :(得分:10)

假设调用者是Python3.5 +,您可以访问更好的subprocess模块。也许您可以使用subprocess.run,并分别通过stdin和stdout发送的pickled Python对象进行通信。会有一些设置要做,但是没有解析你的方面,或者用字符串等等。

这是通过subprocess.Popen

的Python2代码示例
p = subprocess.Popen(python3_args, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
stdout, stderr = p.communicate(pickle.dumps(python3_args))
result = pickle.load(stdout)

答案 2 :(得分:1)

您可以创建一个简单的脚本:

import sys
import my_wrapped_module
import json

params = sys.argv
script = params.pop(0)
function = params.pop(0)
print(json.dumps(getattr(my_wrapped_module, function)(*params)))

你可以这样称呼它:

pythonx.x wrapper.py myfunction param1 param2

这显然是一个安全隐患,但要小心。

另请注意,如果你的params不是字符串或整数,你会遇到一些问题,所以可以考虑将params作为json字符串传输,并使用包装器中的json.loads()进行转换。

答案 3 :(得分:1)

可以使用multiprocessing.managers模块来实现您的目标。它确实需要少量的黑客攻击。

给定一个具有您想要公开的函数的模块,然后您需要创建一个Manager来为这些函数创建代理。

提供py3函数代理的

管理器进程:

from multiprocessing.managers import BaseManager
import spam

class SpamManager(BaseManager):
    pass
# Register a way of getting the spam module.
# You can use the exposed arg to control what is exposed.
# By default only "public" functions (without a leading underscore) are exposed,
# but can only ever expose functions or methods.
SpamManager.register("get_spam", callable=(lambda: spam), exposed=["add", "sub"])

# specifying the address as localhost means the manager is only visible to  
# processes on this machine
manager = SpamManager(address=('localhost', 50000), authkey=b'abc', 
    serializer='xmlrpclib')
server = manager.get_server()
server.serve_forever()

我已将spam重新定义为包含两个名为addsub的函数。

# spam.py
def add(x, y):
    return x + y

def sub(x, y):
    return x - y

使用SpamManager公开的py3函数的客户端进程。

from __future__ import print_function
from multiprocessing.managers import BaseManager

class SpamManager(BaseManager):
    pass
SpamManager.register("get_spam")

m = SpamManager(address=('localhost', 50000), authkey=b'abc', 
    serializer='xmlrpclib')
m.connect()

spam = m.get_spam()
print("1 + 2 = ", spam.add(1, 2)) # prints 1 + 2 = 3
print("1 - 2 = ", spam.sub(1, 2)) # prints 1 - 2 = -1
spam.__name__ # Attribute Error -- spam is a module, but its __name__ attribute 
# is not exposed

设置完成后,此表单提供了一种访问功能和值的简便方法。它还允许以类似的方式使用它们,如果它们不是代理,则可以使用它们。最后,它允许您在服务器进程上设置密码,以便只有授权的进程才能访问管理器。经理长时间运行,也意味着不必为每个函数调用启动新进程。

一个限制是我使用xmlrpclib模块而不是pickle来在服务器和客户端之间来回发送数据。这是因为python2和python3对pickle使用不同的协议。您可以通过将自己的客户端添加到multiprocessing.managers.listener_client来解决这个问题,该客户端使用商定的协议来挑选对象。