Подтвердить что ты не робот

Какое ближайшее я могу назвать функцией 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, но как я могу передать аргументы и получить результаты без слишком большой боли для обработки потока?

4b9b3361

Ответ 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, который использует согласованный протокол для травления объектов.