2016-09-12 36 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來解決此問題,該協議使用商定的協議來進行酸洗對象。

相關問題