从Python 3调用Python 2脚本

56

我有两个脚本,主要的是用Python 3编写的,第二个是用Python 2编写的(它也使用了一个Python 2库)。

Python 2脚本中有一个方法,我想从Python 3脚本中调用,但我不知道如何跨越这个鸿沟。


2
如果在3x中不支持,那么你就无法运行它。 - user4435153
4
另一种方法可能是通过系统调用调用Python 2脚本并将结果存储在文件中……但只有作为最后的手段才这么做。 - tobias_k
2
@GaryYe 如果你需要的输出可以转换为文本文件或类似的东西,那么你可以创建一个Python2脚本来输出你所需的内容(到文件或控制台)。然后,你可以通过subprocess从Python3运行脚本(假设你已经安装了Python 2.x和Python 3.x),并通过subprocess或打开Python 2脚本创建的文件来获取结果。 - avenet
1
没错,只需要像为两种完全不同的语言桥接一样来桥接它...只需要想出一种适用于你要传递的任何数据的接口即可。 - user3012759
问题不太清楚(依我之见)。从Python 3运行Python 2意味着也要调用Python 2解释器。这是你所问的吗? - lmblanes
显示剩余6条评论
9个回答

28

使用execnet非常优雅地调用不同版本的Python。下面的函数可以实现此功能:

import execnet

def call_python_version(Version, Module, Function, ArgumentList):
    gw      = execnet.makegateway("popen//python=python%s" % Version)
    channel = gw.remote_exec("""
        from %s import %s as the_function
        channel.send(the_function(*channel.receive()))
    """ % (Module, Function))
    channel.send(ArgumentList)
    return channel.receive()

示例:一个用Python 2.7编写的my_module.py

def my_function(X, Y): 
    return "Hello %s %s!" % (X, Y)

接下来是以下函数调用:

result = call_python_version("2.7", "my_module", "my_function",  
                             ["Mr", "Bear"]) 
print(result) 
result = call_python_version("2.7", "my_module", "my_function",  
                             ["Mrs", "Wolf"]) 
print(result)

导致

Hello Mr Bear!
Hello Mrs Wolf!

发生的情况是一个“网关”被实例化,等待带有channel.receive()参数列表的到来。一旦它到达,它就被翻译并传递给my_functionmy_function返回它生成的字符串,channel.send(...)将字符串发送回去。在网关的另一侧,channel.receive()捕获结果并将其返回给调用者。调用者最终在Python 3模块中打印由my_function生成的字符串。

2
你能发送和接收除文本以外的对象吗? - jadsq
2
是的,你可以!这就是“execnet”的好处。它以兼容的方式序列化对象。 - Frank-Rene Schäfer
1
有人能评论一下这种方法相对于纯Python 2的性能吗?我认为由于需要启动另一个Python解释器以及序列化和反序列化,肯定会有一些惩罚。 - erobertc
7
execnet 目前处于维护模式,主要是因为它仍然是 pytest-xdist 插件的后端。不要在新项目中使用。” 嗯,现在该怎么办? - ijoseph
@ijoseph: 我不知道这是什么意思。也许你可以使用旧版本或联系作者。 - Frank-Rene Schäfer
显示剩余6条评论

18

您可以使用 subprocess(Python 模块)运行 Python2,方法如下:

Python 3 开始:

#!/usr/bin/env python3
import subprocess

python3_command = "py2file.py arg1 arg2"  # launch your python2 script

process = subprocess.Popen(python3_command.split(), stdout=subprocess.PIPE)
output, error = process.communicate()  # receive output from the python2 script

输出存储Python 2返回的任何内容


2
如果你想在执行下一条Python语句之前等待子进程完成,考虑使用subprocess.call。https://dev59.com/SnVD5IYBdhLWcg3wGXlI#89243 - Matt Kleinsmith
14
我不明白这怎么算是一个答案。这使用的是Python3解释器。 - Matthew Lueder
1
对我来说,它抛出了OSError: [Errno 2] No such file or directory(我正在尝试从Py 2启动Py 3;示例目标脚本只有1个打印语句)。此外,我非常困惑这是否像frakman1所说的那样工作,还是像@MatthewLueder暗示的那样没有工作? - Hack-R
我在使用Python2脚本的绝对路径时遇到了“Exec format error”,但是当我在顶部添加了 #!/usr/bin/env python2.7 这一行后,它就可以正常工作了。 - ChameleonScales
似乎 Python2 文件中的打印语句以字节串的形式出现,需要进行编码。 - ChameleonScales
显示剩余2条评论

12
也许有点晚了,但是调用Python 2.7脚本还有一个简单的方法:
script = ["python2.7", "script.py", "arg1"]    
process = subprocess.Popen(" ".join(script),
                                        shell=True,  
                                        env={"PYTHONPATH": "."})

甚至更晚... 通过“python2.7”,我假设您指的是Python 2.7可执行文件,例如:'“C:\Program Files\Python27\python.exe”'。请注意嵌套引号,以允许Program Files目录中的空格(我仍然认为这是微软有史以来最糟糕的设计之一)。 - JonnyCab

3

我正在使用Python 3运行我的代码,但我需要一个用Python 2.7编写的工具(ocropus)。我尝试了很长时间的subprocess选项,但一直出现错误,脚本无法完成。从命令行可以正常运行。最后我尝试了一些简单的方法,这些方法有效,但我在网上搜索时没有找到。我将ocropus命令放在一个bash脚本中:

#!/bin/bash

/usr/local/bin/ocropus-gpageseg $1

我使用subprocess调用bash脚本。
command = [ocropus_gpageseg_path,  current_path]
process = subprocess.Popen(command,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
output, error = process.communicate()
print('output',output,'error',error)

这确实给了ocropus脚本一个独立的小世界,它似乎需要这样。我发布这篇文章,希望能为其他人节省一些时间。


Shell命令可能存在安全风险,不建议使用。 - lmblanes

2

如果我直接从Python 3环境中调用Python 2可执行文件,那么它对我有效。

python2_command = 'C:\Python27\python.exe python2_script.py arg1'
process = subprocess.Popen(python2_command.split(), stdout=subprocess.PIPE)
output, error = process.communicate()

python3_command = 'python python3_script.py arg1'
process = subprocess.Popen(python3_command.split(), stdout=subprocess.PIPE)
output, error = process.communicate()

1
我在Python3脚本中创建了一个新函数,它包装了Python2.7代码。它可以正确格式化Python2.7代码生成的错误消息,并扩展了mikelsr的答案,使用run(),如subprocess docs所推荐。
在bar.py中(Python2.7代码):
def foo27(input):
    return input * 2

在你的Python3文件中:
import ast
import subprocess

def foo3(parameter):
    try:
        return ast.literal_eval(subprocess.run(
            [
                "C:/path/to/python2.7/python.exe", "-c", # run python2.7 in command mode
                "from bar import foo27;"+
                "print(foo27({}))".format(parameter) # print the output 
            ],
            capture_output=True,
            check=True
        ).stdout.decode("utf-8")) # evaluate the printed output
    except subprocess.CalledProcessError as e:
        print(e.stdout)
        raise Exception("foo27 errored with message below:\n\n{}"
                                .format(e.stderr.decode("utf-8")))

print(foo3(21))
# 42

当传递简单的Python对象作为参数时,这个方法可以正常工作,例如字典,但是对于由类创建的对象(例如numpy数组)无法正常工作,它们必须被序列化并在屏障的另一侧重新实例化。

0
注意:在liclipse IDE中运行我的Python 2.x软件时出现了这个问题。当我从命令行上的bash脚本运行它时,它没有这个问题。这是我在混合使用Python 2.x和3.x脚本时遇到的一个问题和解决方案。
我正在运行一个Python 2.6进程,并需要调用/执行一个Python 3.6脚本。环境变量PYTHONPATH被设置为指向2.6 Python软件,因此它在以下内容上出现了问题:
File "/usr/lib64/python2.6/encodings/__init__.py", line 123
raise CodecRegistryError,\

这导致了3.6版本的Python脚本失败。 因此,我没有直接调用3.6程序,而是创建了一个Bash脚本来清除PYTHONPATH环境变量。

#!/bin/bash
export PYTHONPATH=
## Now call the 3.6 python scrtipt
./36psrc/rpiapi/RPiAPI.py $1

0

如果你不想在Python 3中调用它们,可以通过创建批处理文件在conda env批处理中运行它们,如下所示:

call C:\ProgramData\AnacondaNew\Scripts\activate.bat

C:\Python27\python.exe "script27.py" C:\ProgramData\AnacondaNew\python.exe "script3.py"

call conda deactivate

pause


-2

有时会出现问题。一些 Python2 模块在 Python 3 上无法运行。问题中的脚本使用了 Python2 库,所以这不会起作用。 - Andy Zhang

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接