2016-09-12 35 views
19

说我有两个文件:什么是最接近我可以使用不同的Python版本调用Python函数?

# 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 

... 

现在假设我想打电话给spambeans内。这不是直接可能的,因为这两个文件都依赖于不兼容的Python版本。当然,我可以Popen不同的蟒蛇的过程,但我怎么能传递的参数,并且没有太多的流解析疼痛检索结果?

+0

是你在backports中的函数吗? –

回答

12

下面是使用subprocesspickle我实际测试过一个完整的示例实现。请注意,您需要明确使用协议版本2来在Python 3端进行酸洗(至少对于组合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.listdir

# beans.py 

import py3bridge 

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

非常好。我遇到了一些有关环境变量的问题(Python2环境是嵌入到另一个程序中的本地安装,它覆盖了'PATH'等),但是可以通过用'['env'替换直接的'python3' Popen调用来解决这个问题。 ,'-i','bash','-l','-c','python3 -m py3bridge'+ self.mod_name +''+ self.func_name]'。 – leftaroundabout

+0

@leftaroundabout还有'subprocess.Popen'的'env'参数,您可以在其中传递子进程的环境变量。 –

+0

啊,但也可以用来简单地忽略给定的参数/恢复到登录默认值? – leftaroundabout

10

假设主叫方是Python3.5 +,你有机会获得一个更好的subprocess模块。也许你可以用户subprocess.run,并且通过通过stdin和stdout,分别发送腌Python对象沟通。会有一些设置的事情,但没有分析就在你身边,或用绳子等碴

这里是Python2代码示例通过subprocess.Popen

p = subprocess.Popen(python3_args, stdin=subprocess.PIPE, stdout=subprocess.PIPE) 
stdout, stderr = p.communicate(pickle.dumps(python3_args)) 
result = pickle.load(stdout) 
+0

其实我在我的例子中写错了版本:我真的希望从python2中调用python3。无论如何,我希望这没有真正的区别...当然,酸洗也是这个方向的作用?如果没有,只有调用者是Python3才有效的答案也会有帮助(也许我可以实际上扭转依赖)。 – leftaroundabout

+1

使用pickled对象的选项仍然存在,只需设置stdin并读取子进程的stdout就会很困难,因为您必须使用subprocess.call或subprocess.Popen,它们的接口较为复杂。你仍然避免做手动解析,因为问题的症结仍然是调用pickle.dumps/loads,这只是一点点的代码。 –

+2

@leftaroundabout在Python 2.4中添加了['subprocess'](https://docs.python.org/2/library/subprocess.html)模块,所以似乎没有一个原因可以解释为什么你不能在你的情况下使用它。 – ray

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 

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

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

+0

看起来不错。但是,当这些参数包含大量数据时,我不认为这是合适的?对于我现在心目中的应用程序来说,它可能没问题,但通常我会觉得不舒服通过命令行传递如此大的json编码的字符串。 – leftaroundabout

+0

我自己会觉得不舒服!哈哈。也许它可以帮助,但最好的解决方案可能是使用'2to3'来转换你的python2库。 –

+1

我肯定宁愿将所有东西都迁移到Python3,但这不是一个真正的选择,因为该特定的Python2引擎是嵌入在第三方C++项目中的修改版本。 – leftaroundabout

1

它可以使用multiprocessing.managers模块要达到什么

# spam.py 

import pathlib 

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

Python的2模块你要。它确实需要少量的黑客攻击。

既然有要公开功能的模块,那么你需要创建一个Manager可以创建为这些功能的代理。

,供应来代理PY3功能

管理器进程:

​​

我已经重新定义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来解决此问题,该协议使用商定的协议来进行酸洗对象。

相关问题