使用spawnSync从Node.js调用Python函数时减少开销
我正在使用Python 3.10,并且在我的项目目录中有一个名为 utilities.py
的文件,里面包含了几个函数和一些导入语句。我在其他Python文件中导入这个文件的内容,下面的代码片段展示了 validate_user.py
文件的内容。我是在一个环境中调用Python函数,使用的是nodeJS中的 spawnSync
函数,这样可以把参数从JS环境传递到Python环境。但是,问题是这个过程执行得太慢了。
举个例子,基于存储在sqlite数据库中的凭证来验证用户(见下面的 validate_user()
函数),大约需要12到13秒。这个例子中,数据库很小,只包含一个用户的数据。然而,当我使用另一种方法,根据保存在我的.bashrc文件中的凭证来验证用户(见下面的 validate_user_env()
函数)时,验证几乎是瞬间完成的!
此外,当我尝试将 validate_user.py
文件中所有必要的函数和库导入从 utilities.py
中引入时,基于数据库的验证也变得瞬间完成了!
最后一次实验中,我把大约25个导入语句从 utilities.py
复制到 validate_user.py
中,只是想看看这会有什么影响,这次验证又变得更慢了,这表明 validate_user.py
中的导入语句是导致延迟的主要原因。
我该如何解决这个问题?在我的实际项目中,我使用之前提到的 spawnSync
方法调用分布在多个文件中的许多Python函数。它们中的许多使用了在其他文件中定义的公共函数,而这些文件也包含多个导入语句,所以在不同文件中重复代码是没有意义的。有什么好的策略可以解决这个问题吗?
// JS code to call Python
function callSync(PYTHON_PATH, script, args, options) {
const result = spawnSync(PYTHON_PATH, [script, JSON.stringify(args)]);
if (result.status === 0) {
const ret = result.stdout.toString();
if (!(ret instanceof Error)) {
return ret;
} else {
return {
error: ret.message
};
}
} else if (result.status === 1) {
return {
error: result.stderr.toString()
};
} else {
return {
error: result.stdout.toString()
};
}
}
## Below is the content of validate_user.py
import argparse
import json
import os
import utilities as ut
## This function uses a DB to validate
def validate_user(args):
## Get the arguments from JS
username = args.get('username')
password = args.get('password')
res = ut.validate_user_password(username, password)
result = {
"greeting": "Hello from validate_user.py!",
"code": res["code"],
"message": res["msg"]
}
print(json.dumps(result))
## This function uses env valiables to validate
def validate_user_env(args):
## Get the arguments from JS
username_in = args.get('username')
password_in = args.get('password')
## Get the credentials from env vars
username = os.getenv("my_username")
password = os.getenv("my_password")
## validate and return
if username_in == username and password_in == password:
result = {"greeting": "Hello from validate_user.py!", "code": "success", "message": "Authentication successful"}
else:
result = {"greeting": "Hello from validate_user.py!", "code": "failure", "message": "Invalid username or password"}
print(json.dumps(result))
## Set up argument parser object and fetch the args
parser = argparse.ArgumentParser()
parser.add_argument('json_args')
args = parser.parse_args()
json_args = json.loads(args.json_args)
validate_user(json_args)
2 个回答
如果你已经调查过并确定问题出在导入上,那你可以跳过这部分。不过,我建议你记录一下每个工作部分花费的时间和日志。
import timeit
start_time = timeit.default_timer()
# rest of your imports and code between
elapsed_time = timeit.default_timer() - start_time
# might need to write to file if running from Node
print(elapsed_time) # seconds
你可以在多个地方添加这些记录,以帮助找出脚本中哪个部分是瓶颈。
关于导入,我建议你检查一下是否有一些不该在导入时运行的代码。例如:
# file1.py
import time
def long_running_function():
time.sleep(5) # simulate lots of work
long_running_function() # note this is being called in the file
然后在你执行的文件中
# file2.py
print('Before import')
import file1
print('After import') # this will take 5 seconds to output
为了防止这种情况,你应该添加一个主保护。
if __name__ == '__main__':
long_running_function()
这样,如果你单独执行 file1.py
,long_running_function
就会运行;但如果你通过运行 file2.py
导入 file1
,那么 long_running_function
就不会运行。
就像你在 validate_user.py
中做的那样,设置 ArgumentParser 并调用 validate_user
,如果其他文件导入你的 validate_user.py
,那么它也会运行,即使你并不想让它运行。
为什么你不在Node.js中只导入一次那个Python文件,然后在你整个Node.js应用的生命周期中使用它呢?
顺便说一下,我为此写了一个库:Py3
简单用法:
# simple.py
import json
def validate():
# props are auto json-ed
return {'hi': 'hello', 'num': 10, 'bool': True}
另一边:
// index.js
import { Py } from "py3";
// Create a Py instance
const python = new Py({
cwd: import.meta.dirname
});
// Execute a simple Python instruction
(async () => {
await python.waitStart();
await python.exec("from simple import validate");
// props are auto unjson-ed
console.log((await python.exec(`validate()`)).hi);
})();