两个独立的 Python 引擎之间的通信
Communication between two separate Python engines
问题陈述如下:
我正在使用 Abaqus,这是一个用于分析机械问题的程序。它基本上是一个独立的 Python 解释器,有自己的对象等。在这个程序中,我 运行 一个 python 脚本来设置我的分析(所以这个脚本可以修改)。它还包含一个在接收到外部信号时必须执行的方法。这些信号来自我在自己的 Python 引擎中 运行ning 的主脚本。
目前,我有以下工作流程:
当 Abaqus 脚本必须执行特定函数时,主脚本将一个布尔值设置为 True,并将该布尔值提取到一个文件中。 Abaqus 脚本定期检查此文件以查看布尔值是否已设置为 true。如果是这样,它会进行分析并腌制输出,以便主脚本可以读取此输出并对其执行操作。
我正在寻找一种更有效的方式来通知其他进程开始分析,因为有很多不必要的检查正在进行。通过 pickle 进行数据交换对我来说不是问题,但当然欢迎更有效的解决方案。
搜索结果总是给我带有子进程之类的解决方案,这是针对在同一个解释器中启动的两个进程。我也看过 ZeroMQ,因为它应该可以实现这样的事情,但我认为这太过分了,想要 python 中的解决方案。两个解释器都是 运行ning python 2.7(虽然版本不同)
明确地说,我的理解是您通过 Python 脚本 运行ning Abaqus/CAE 作为一个独立的进程(我们称之为 abq.py
),这检查、打开和读取触发器文件以确定它是否应该 运行 分析。触发器文件由第二个 Python 进程创建(我们称之为 main.py
)。最后,main.py
等待读取由 abq.py
创建的输出文件。您想要一种更有效的方式来向 abq.py
发出信号 运行 分析,并且您愿意接受不同的技术来交换数据。
正如您所提到的,子进程或多进程可能是一个选项。但是,我认为更简单的解决方案是组合您的两个脚本,并可选择使用回调函数来监视解决方案并处理您的输出。我假设没有必要将 abq.py
不断 运行 宁作为一个单独的过程,并且所有分析都可以在适当的时候从 main.py
开始。
让 main.py
可以访问 Abaqus Mdb。如果它已经构建,你打开它:
mdb = openMdb(FileName)
如果main.py
开始所有分析,则不需要触发文件。例如:
if SomeCondition:
j = mdb.Job(name=MyJobName, model=MyModelName)
j.submit()
j.waitForCompletion()
完成后,main.py
可以读取输出文件并继续。如果数据文件是由分析本身生成的(例如 .dat
或 .odb
文件),这很简单。 OTH,如果输出文件是由您当前 abq.py
中的某些代码生成的,那么您可以将它包含在 main.py
中。
如果这不能提供足够的控制,您可以将回调函数添加到 monitorManager 对象(导入 abaqus 模块时自动创建:from abaqus import *
,而不是 waitForCompletion
方法).这允许您监视和响应来自求解器的各种消息,例如 COMPLETED
、ITERATION
等。回调函数定义如下:
def onMessage(jobName, messageType, data, userData):
if messageType == COMPLETED:
# do stuff
else:
# other stuff
然后将其添加到 monitorManager 并调用作业:
monitorManager.addMessageCallback(jobName=MyJobName,
messageType=ANY_MESSAGE_TYPE, callback=onMessage, userData=MyDataObj)
j = mdb.Job(name=MyJobName, model=MyModelName)
j.submit()
这种方法的好处之一是您可以将 Python 对象作为 userData
参数传递。这可能是您的输出文件或其他一些数据容器。您可能会弄清楚如何在回调函数中处理输出数据 - 例如,访问 Odb 并获取数据,然后根据需要进行任何操作,而根本不需要外部文件。
编辑:
和@MattP一样,我将添加我的理解声明:
背景
我相信您正在 运行使用一个名为 abaqus 的产品。 abaqus 产品包括一个链接的 python 解释器,您可以通过某种方式访问它(可能通过在命令行上 运行ning abaqus python foo.py
)。
您还可以在同一台机器上进行单独的 python 安装。您正在开发 numpy/scipy 到 python 安装上的 运行 的代码。
这两个安装是不同的:它们有不同的二进制解释器、不同的库、不同的安装路径等。但是它们位于同一台物理主机上。
您的 objective 是为了让您编写的 "plain python" 程序能够与 "Abaqus python" 环境中的一个或多个脚本 运行ning 进行通信,以便这些脚本可以在 Abaqus 系统内执行工作,并且 return 结果。
解决方案
这是一个基于套接字的解决方案。有两部分,abqlistener.py
和 abqclient.py
。这种方法的优点是它使用定义明确的机制 "waiting for work." 不轮询文件等。它是 "hard" API。您可以从同一台机器上的进程连接到侦听器进程,运行使用相同版本的 python,或者从不同的机器,或者从不同版本的 python,或者来自 ruby 或 C 或 perl 甚至 COBOL。它允许您将真实的 "air gap" 放入您的系统中,因此您可以以最小的耦合开发这两个部分。
服务器部分是abqlistener
。目的是将部分代码复制到 Abaqus 脚本中。然后 abq 进程将成为服务器,侦听特定端口号上的连接,并进行响应。是否发回回复。等等。
我不确定您是否需要为每项工作做设置工作。如果是这样,那将必须是连接的一部分。这只会启动 ABQ,监听一个端口(永远),并处理请求。任何特定于工作的设置都必须是工作流程的一部分。 (也许发送一个参数字符串,或者一个配置文件的名称,或者其他什么。)
客户端部分是abqclient
。这可以移动到一个模块中,或者只是 copy/pasted 到您现有的非 ABQ 程序代码中。基本上,您打开一个连接到正确的 host:port 组合,并且您正在与服务器对话。发送一些数据,取回一些数据等
这些东西主要是从在线示例代码中提取的。因此,如果您开始深入研究任何内容,它应该看起来非常熟悉。
这是abqlistener.py:
# The below usage example is completely bogus. I don't have abaqus, so
# I'm just running python2.7 abqlistener.py [options]
usage = """
abacus python abqlistener.py [--host 127.0.0.1 | --host mypc.example.com ] \
[ --port 2525 ]
Sets up a socket listener on the host interface specified (default: all
interfaces), on the given port number (default: 2525). When a connection
is made to the socket, begins processing data.
"""
import argparse
parser = argparse.ArgumentParser(description='Abacus listener',
add_help=True,
usage=usage)
parser.add_argument('-H', '--host', metavar='INTERFACE', default='',
help='Interface IP address or name, or (default: empty string)')
parser.add_argument('-P', '--port', metavar='PORTNUM', type=int, default=2525,
help='port number of listener (default: 2525)')
args = parser.parse_args()
import SocketServer
import json
class AbqRequestHandler(SocketServer.BaseRequestHandler):
"""Request handler for our socket server.
This class is instantiated whenever a new connection is made, and
must override `handle(self)` in order to handle communicating with
the client.
"""
def do_work(self, data):
"Do some work here. Call abaqus, whatever."
print "DO_WORK: Doing work with data!"
print data
return { 'desc': 'low-precision natural constants','pi': 3, 'e': 3 }
def handle(self):
# Allow the client to send a 1kb message (file path?)
self.data = self.request.recv(1024).strip()
print "SERVER: {} wrote:".format(self.client_address[0])
print self.data
result = self.do_work(self.data)
self.response = json.dumps(result)
print "SERVER: response to {}:".format(self.client_address[0])
print self.response
self.request.sendall(self.response)
if __name__ == '__main__':
print args
server = SocketServer.TCPServer((args.host, args.port), AbqRequestHandler)
print "Server starting. Press Ctrl+C to interrupt..."
server.serve_forever()
这里是 abqclient.py
:
usage = """
python2.7 abqclient.py [--host HOST] [--port PORT]
Connect to abqlistener on HOST:PORT, send a message, wait for reply.
"""
import argparse
parser = argparse.ArgumentParser(description='Abacus listener',
add_help=True,
usage=usage)
parser.add_argument('-H', '--host', metavar='INTERFACE', default='',
help='Interface IP address or name, or (default: empty string)')
parser.add_argument('-P', '--port', metavar='PORTNUM', type=int, default=2525,
help='port number of listener (default: 2525)')
args = parser.parse_args()
import json
import socket
message = "I get all the best code from Whosebug!"
print "CLIENT: Creating socket..."
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print "CLIENT: Connecting to {}:{}.".format(args.host, args.port)
s.connect((args.host, args.port))
print "CLIENT: Sending message:", message
s.send(message)
print "CLIENT: Waiting for reply..."
data = s.recv(1024)
print "CLIENT: Got response:"
print json.loads(data)
print "CLIENT: Closing socket..."
s.close()
下面是我 运行 他们一起打印的内容:
$ python2.7 abqlistener.py --port 3434 &
[2] 44088
$ Namespace(host='', port=3434)
Server starting. Press Ctrl+C to interrupt...
$ python2.7 abqclient.py --port 3434
CLIENT: Creating socket...
CLIENT: Connecting to :3434.
CLIENT: Sending message: I get all the best code from Whosebug!
CLIENT: Waiting for reply...
SERVER: 127.0.0.1 wrote:
I get all the best code from Whosebug!
DO_WORK: Doing work with data!
I get all the best code from Whosebug!
SERVER: response to 127.0.0.1:
{"pi": 3, "e": 3, "desc": "low-precision natural constants"}
CLIENT: Got response:
{u'pi': 3, u'e': 3, u'desc': u'low-precision natural constants'}
CLIENT: Closing socket...
参考文献:
argparse
, SocketServer
, json
, socket
都是"standard"Python库。
我同意这个答案,除了一些小的语法问题。
在处理程序中定义实例变量是不行的。更不用说它们没有在任何类型的 init() 方法中定义。子类化 TCPServer 并在 TCPServer.init() 中定义您的实例变量。其他一切都将相同。
问题陈述如下:
我正在使用 Abaqus,这是一个用于分析机械问题的程序。它基本上是一个独立的 Python 解释器,有自己的对象等。在这个程序中,我 运行 一个 python 脚本来设置我的分析(所以这个脚本可以修改)。它还包含一个在接收到外部信号时必须执行的方法。这些信号来自我在自己的 Python 引擎中 运行ning 的主脚本。
目前,我有以下工作流程: 当 Abaqus 脚本必须执行特定函数时,主脚本将一个布尔值设置为 True,并将该布尔值提取到一个文件中。 Abaqus 脚本定期检查此文件以查看布尔值是否已设置为 true。如果是这样,它会进行分析并腌制输出,以便主脚本可以读取此输出并对其执行操作。
我正在寻找一种更有效的方式来通知其他进程开始分析,因为有很多不必要的检查正在进行。通过 pickle 进行数据交换对我来说不是问题,但当然欢迎更有效的解决方案。
搜索结果总是给我带有子进程之类的解决方案,这是针对在同一个解释器中启动的两个进程。我也看过 ZeroMQ,因为它应该可以实现这样的事情,但我认为这太过分了,想要 python 中的解决方案。两个解释器都是 运行ning python 2.7(虽然版本不同)
明确地说,我的理解是您通过 Python 脚本 运行ning Abaqus/CAE 作为一个独立的进程(我们称之为 abq.py
),这检查、打开和读取触发器文件以确定它是否应该 运行 分析。触发器文件由第二个 Python 进程创建(我们称之为 main.py
)。最后,main.py
等待读取由 abq.py
创建的输出文件。您想要一种更有效的方式来向 abq.py
发出信号 运行 分析,并且您愿意接受不同的技术来交换数据。
正如您所提到的,子进程或多进程可能是一个选项。但是,我认为更简单的解决方案是组合您的两个脚本,并可选择使用回调函数来监视解决方案并处理您的输出。我假设没有必要将 abq.py
不断 运行 宁作为一个单独的过程,并且所有分析都可以在适当的时候从 main.py
开始。
让 main.py
可以访问 Abaqus Mdb。如果它已经构建,你打开它:
mdb = openMdb(FileName)
如果main.py
开始所有分析,则不需要触发文件。例如:
if SomeCondition:
j = mdb.Job(name=MyJobName, model=MyModelName)
j.submit()
j.waitForCompletion()
完成后,main.py
可以读取输出文件并继续。如果数据文件是由分析本身生成的(例如 .dat
或 .odb
文件),这很简单。 OTH,如果输出文件是由您当前 abq.py
中的某些代码生成的,那么您可以将它包含在 main.py
中。
如果这不能提供足够的控制,您可以将回调函数添加到 monitorManager 对象(导入 abaqus 模块时自动创建:from abaqus import *
,而不是 waitForCompletion
方法).这允许您监视和响应来自求解器的各种消息,例如 COMPLETED
、ITERATION
等。回调函数定义如下:
def onMessage(jobName, messageType, data, userData):
if messageType == COMPLETED:
# do stuff
else:
# other stuff
然后将其添加到 monitorManager 并调用作业:
monitorManager.addMessageCallback(jobName=MyJobName,
messageType=ANY_MESSAGE_TYPE, callback=onMessage, userData=MyDataObj)
j = mdb.Job(name=MyJobName, model=MyModelName)
j.submit()
这种方法的好处之一是您可以将 Python 对象作为 userData
参数传递。这可能是您的输出文件或其他一些数据容器。您可能会弄清楚如何在回调函数中处理输出数据 - 例如,访问 Odb 并获取数据,然后根据需要进行任何操作,而根本不需要外部文件。
编辑:
和@MattP一样,我将添加我的理解声明:
背景
我相信您正在 运行使用一个名为 abaqus 的产品。 abaqus 产品包括一个链接的 python 解释器,您可以通过某种方式访问它(可能通过在命令行上 运行ning abaqus python foo.py
)。
您还可以在同一台机器上进行单独的 python 安装。您正在开发 numpy/scipy 到 python 安装上的 运行 的代码。
这两个安装是不同的:它们有不同的二进制解释器、不同的库、不同的安装路径等。但是它们位于同一台物理主机上。
您的 objective 是为了让您编写的 "plain python" 程序能够与 "Abaqus python" 环境中的一个或多个脚本 运行ning 进行通信,以便这些脚本可以在 Abaqus 系统内执行工作,并且 return 结果。
解决方案
这是一个基于套接字的解决方案。有两部分,abqlistener.py
和 abqclient.py
。这种方法的优点是它使用定义明确的机制 "waiting for work." 不轮询文件等。它是 "hard" API。您可以从同一台机器上的进程连接到侦听器进程,运行使用相同版本的 python,或者从不同的机器,或者从不同版本的 python,或者来自 ruby 或 C 或 perl 甚至 COBOL。它允许您将真实的 "air gap" 放入您的系统中,因此您可以以最小的耦合开发这两个部分。
服务器部分是abqlistener
。目的是将部分代码复制到 Abaqus 脚本中。然后 abq 进程将成为服务器,侦听特定端口号上的连接,并进行响应。是否发回回复。等等。
我不确定您是否需要为每项工作做设置工作。如果是这样,那将必须是连接的一部分。这只会启动 ABQ,监听一个端口(永远),并处理请求。任何特定于工作的设置都必须是工作流程的一部分。 (也许发送一个参数字符串,或者一个配置文件的名称,或者其他什么。)
客户端部分是abqclient
。这可以移动到一个模块中,或者只是 copy/pasted 到您现有的非 ABQ 程序代码中。基本上,您打开一个连接到正确的 host:port 组合,并且您正在与服务器对话。发送一些数据,取回一些数据等
这些东西主要是从在线示例代码中提取的。因此,如果您开始深入研究任何内容,它应该看起来非常熟悉。
这是abqlistener.py:
# The below usage example is completely bogus. I don't have abaqus, so
# I'm just running python2.7 abqlistener.py [options]
usage = """
abacus python abqlistener.py [--host 127.0.0.1 | --host mypc.example.com ] \
[ --port 2525 ]
Sets up a socket listener on the host interface specified (default: all
interfaces), on the given port number (default: 2525). When a connection
is made to the socket, begins processing data.
"""
import argparse
parser = argparse.ArgumentParser(description='Abacus listener',
add_help=True,
usage=usage)
parser.add_argument('-H', '--host', metavar='INTERFACE', default='',
help='Interface IP address or name, or (default: empty string)')
parser.add_argument('-P', '--port', metavar='PORTNUM', type=int, default=2525,
help='port number of listener (default: 2525)')
args = parser.parse_args()
import SocketServer
import json
class AbqRequestHandler(SocketServer.BaseRequestHandler):
"""Request handler for our socket server.
This class is instantiated whenever a new connection is made, and
must override `handle(self)` in order to handle communicating with
the client.
"""
def do_work(self, data):
"Do some work here. Call abaqus, whatever."
print "DO_WORK: Doing work with data!"
print data
return { 'desc': 'low-precision natural constants','pi': 3, 'e': 3 }
def handle(self):
# Allow the client to send a 1kb message (file path?)
self.data = self.request.recv(1024).strip()
print "SERVER: {} wrote:".format(self.client_address[0])
print self.data
result = self.do_work(self.data)
self.response = json.dumps(result)
print "SERVER: response to {}:".format(self.client_address[0])
print self.response
self.request.sendall(self.response)
if __name__ == '__main__':
print args
server = SocketServer.TCPServer((args.host, args.port), AbqRequestHandler)
print "Server starting. Press Ctrl+C to interrupt..."
server.serve_forever()
这里是 abqclient.py
:
usage = """
python2.7 abqclient.py [--host HOST] [--port PORT]
Connect to abqlistener on HOST:PORT, send a message, wait for reply.
"""
import argparse
parser = argparse.ArgumentParser(description='Abacus listener',
add_help=True,
usage=usage)
parser.add_argument('-H', '--host', metavar='INTERFACE', default='',
help='Interface IP address or name, or (default: empty string)')
parser.add_argument('-P', '--port', metavar='PORTNUM', type=int, default=2525,
help='port number of listener (default: 2525)')
args = parser.parse_args()
import json
import socket
message = "I get all the best code from Whosebug!"
print "CLIENT: Creating socket..."
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print "CLIENT: Connecting to {}:{}.".format(args.host, args.port)
s.connect((args.host, args.port))
print "CLIENT: Sending message:", message
s.send(message)
print "CLIENT: Waiting for reply..."
data = s.recv(1024)
print "CLIENT: Got response:"
print json.loads(data)
print "CLIENT: Closing socket..."
s.close()
下面是我 运行 他们一起打印的内容:
$ python2.7 abqlistener.py --port 3434 &
[2] 44088
$ Namespace(host='', port=3434)
Server starting. Press Ctrl+C to interrupt...
$ python2.7 abqclient.py --port 3434
CLIENT: Creating socket...
CLIENT: Connecting to :3434.
CLIENT: Sending message: I get all the best code from Whosebug!
CLIENT: Waiting for reply...
SERVER: 127.0.0.1 wrote:
I get all the best code from Whosebug!
DO_WORK: Doing work with data!
I get all the best code from Whosebug!
SERVER: response to 127.0.0.1:
{"pi": 3, "e": 3, "desc": "low-precision natural constants"}
CLIENT: Got response:
{u'pi': 3, u'e': 3, u'desc': u'low-precision natural constants'}
CLIENT: Closing socket...
参考文献:
argparse
, SocketServer
, json
, socket
都是"standard"Python库。
我同意这个答案,除了一些小的语法问题。
在处理程序中定义实例变量是不行的。更不用说它们没有在任何类型的 init() 方法中定义。子类化 TCPServer 并在 TCPServer.init() 中定义您的实例变量。其他一切都将相同。