使用不同的 Python 版本最接近调用 Python 函数的方法是什么?
What's the closest I can get to calling a Python function using a different Python version?
假设我有两个文件:
# 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
...
现在假设我想从 beans
中调用 spam
。这不是直接可能的,因为这两个文件都依赖于不兼容的 Python 版本。当然,我可以 Popen
一个不同的 python 过程,但是我怎样才能传递参数并检索结果而不会造成太多的流解析痛苦?
假设调用者是 Python3.5+,您可以访问更好的 subprocess 模块。也许您可以使用 subprocess.run
,并通过分别通过 stdin 和 stdout 发送的 pickled 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)
您可以创建一个简单的脚本:
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()
进行转换在包装纸中。
这是我实际测试过的使用 subprocess
和 pickle
的完整示例实现。请注意,您需要在 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()]
Python2模块使用spam.listdir
# beans.py
import py3bridge
delegate = py3bridge.Py3Wrapper('spam', 'listdir')
py3result = delegate('.')
print py3result
可以使用 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
以包含两个名为 add
和 sub
的函数。
# 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
来解决此问题,该客户端使用商定的酸洗对象协议。
假设我有两个文件:
# 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
...
现在假设我想从 beans
中调用 spam
。这不是直接可能的,因为这两个文件都依赖于不兼容的 Python 版本。当然,我可以 Popen
一个不同的 python 过程,但是我怎样才能传递参数并检索结果而不会造成太多的流解析痛苦?
假设调用者是 Python3.5+,您可以访问更好的 subprocess 模块。也许您可以使用 subprocess.run
,并通过分别通过 stdin 和 stdout 发送的 pickled 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)
您可以创建一个简单的脚本:
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()
进行转换在包装纸中。
这是我实际测试过的使用 subprocess
和 pickle
的完整示例实现。请注意,您需要在 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()]
Python2模块使用spam.listdir
# beans.py
import py3bridge
delegate = py3bridge.Py3Wrapper('spam', 'listdir')
py3result = delegate('.')
print py3result
可以使用 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
以包含两个名为 add
和 sub
的函数。
# spam.py
def add(x, y):
return x + y
def sub(x, y):
return x - y
使用 SpamManager
.
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
来解决此问题,该客户端使用商定的酸洗对象协议。