Какое ближайшее я могу назвать функцией 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
...
Теперь предположим, что я хочу вызвать spam
из beans
. Это невозможно, так как оба файла зависят от несовместимых версий Python. Конечно, я могу Popen
использовать другой процесс python, но как я могу передать аргументы и получить результаты без слишком большой боли для обработки потока?
Ответы
Ответ 1
Вот полная реализация примера с использованием subprocess
и pickle
, которые я фактически протестировал. Обратите внимание, что вам нужно использовать протокол версии 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.py
import pathlib
def listdir(p):
return [str(c) for c in pathlib.Path(p).iterdir()]
Модуль Python 2 с использованием spam.listdir
# beans.py
import py3bridge
delegate = py3bridge.Py3Wrapper('spam', 'listdir')
py3result = delegate('.')
print py3result
Ответ 2
Предполагая, что вызывающим является Python3.5 +, у вас есть доступ к более приятному subprocess модулю. Возможно, вы могли бы пользователь subprocess.run
и общаться через маринованные объекты Python, отправленные через stdin и stdout, соответственно. Будет некоторая настройка, но не будет синтаксического разбора на вашей стороне, или будет удалять строки и т.д.
Вот пример кода 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)
Ответ 3
Вы можете создать простой script как таковой:
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()
в оболочку.
Ответ 4
Можно использовать модуль 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
клиентский процесс, который использует функции py3, открытые 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
, который использует согласованный протокол для травления объектов.