使用spawnSync从Node.js调用Python函数时减少开销

0 投票
2 回答
36 浏览
提问于 2025-04-12 14:59

我正在使用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 个回答

0

如果你已经调查过并确定问题出在导入上,那你可以跳过这部分。不过,我建议你记录一下每个工作部分花费的时间和日志。

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.pylong_running_function 就会运行;但如果你通过运行 file2.py 导入 file1,那么 long_running_function 就不会运行。

就像你在 validate_user.py 中做的那样,设置 ArgumentParser 并调用 validate_user,如果其他文件导入你的 validate_user.py,那么它也会运行,即使你并不想让它运行。

0

为什么你不在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);
})();

撰写回答