page contents

phpy:连接 PHP 与 Python 生态

本文讲述了phpy:连接 PHP 与python生态!具有很好的参考价值,希望对大家有所帮助。一起跟随六星小编过来看看吧,具体如下:

attachments-2023-12-uuGahgSX6577f768a3032.jpg本文讲述了phpy:连接 PHP 与python生态!具有很好的参考价值,希望对大家有所帮助。一起跟随六星小编过来看看吧,具体如下:

上周我们发布了 phpy 项目的第一个版本,收到了大量 PHP 开发者的关注,许多开发者向我们提交问题、意见和建议、Issues 和 Pull Request。

经过一周的优化和修改完善,phpy 又得到了一些突破性进展。本文将详细解答大家广泛关注的问题,以及介绍第二个版本的变化:

运行原理

在进程内同时创建了 ZendVM 和 CPython VM,直接在进程堆栈空间内使用 C 函数 互相调用, 开销只有 zval <-> PyObject 结构体转换,因此性能是非常高的。


phpy 基于 PHP 官方的 ZendAPI 和 Python 官方的 Py C API 实现,没有其他外部的 C 库依赖。因此是可以实现跨平台的,Linux、Windows、macOS 均可使用。


在 PHP-FPM 下使用

第一个版本中我们不建议在 PHP-FPM 环境下使用。在后续的测试发现在 PHP-FPM 环境下 import Python 包,仅第一次消耗比较多的时间,第二次直接使用了 Python sys.modules 中缓存的包,因此 phpy 是完全可以用于 PHP-FPM 或 Apache 等短生命周期环境下的。


甚至我们可以使用 phpy 使得一些对象在 PHP-FPM 下也能常驻内存。


$app = PyCore::import('app.user');

$storage = $app->storage;

if (!isset($storage['data'])) {

    $storage['data'] = uniqid();

    var_dump("no cache");

    $o = new stdClass();

    $o->hello = uniqid();

    $storage['obj'] = $o;

} else {

    var_dump("cached");

    var_dump(strval($storage['data']));

    var_dump($storage['obj']);

}

上面的代码将一个 Python 字典持久化了,在 PHP-FPM 下可以实现内存复用。


phpy 底层设置了内存安全边界,若 Python 中持久化了 PHP 对象,在请求结束后依然会销毁,并且将值设置为 NULL,不必担心出现内存错误

性能测试

压测脚本中创建了一个 PyDict ,分别读写 PHP 代码和 Python 代码执行 1000万次。


PHP 版本:PHP 8.2.3 (cli) (built: Mar 17 2023 15:06:57) (NTS)


Python 版本:Python 3.11.5


操作系统:Ubuntu 20.04


GCC 版本:gcc version 9.4.0 (Ubuntu 9.4.0-1ubuntu1~20.04.2)


请注意此测试需要构造一个 1000 万元素的 HashTable,需要至少 2G 以上内存空间才可以运行

测试代码请参考 压力测试

结果对比

(base) htf@swoole-12:~/workspace/python-php/docs/benchmark$ php dict.php 

dict set: 4.663758 seconds

dict get: 3.980076 seconds

(base) htf@swoole-12:~/workspace/python-php/docs/benchmark$ php array.php 

array set: 1.578963 seconds

array get: 0.831129 seconds

(base) htf@swoole-12:~/workspace/python-php/docs/benchmark$ python dict.py 

dict set: 5.321664 seconds

dict get: 4.969081 seconds

(base) htf@swoole-12:~/workspace/python-php/docs/benchmark$

以 Python 测试为基准:


脚本名称SetGet

dict.php114%125%

array.php337%599%

phpy 以 PHP 代码写入 PyDict 的性能比原生 Python 高 14%,读取性能高 25%


PHP 写入 PHP Array 的性能比 Python 写入 Dict 高 237%,读取高出了近 500%


异常捕获

最新版本支持了 Python 和 PHP 异常的融合,可以在 PHP 代码中捕获 Python 运行过程中触发的异常。


try {

    PyCore::import('not_exists');

} catch (PyError $e) {

    PyCore::print($e->error);

    PyCore::print($e->type);

    PyCore::print($e->value);

    PyCore::print($e->traceback);

}

底层会自动将 $e->value 的字符串值设置为异常消息,可使用 $e->getMessage() 获取

PyError 未设置 $e->code 错误码,请勿使用

IDE 自动提示

phpy 提供了一个自动生成工具,可以生成 IDE 自动提示文件。使用方法:

cd phpy/tools

php gen-lib.php [Python 包名称]

例如 matplotlib.pyplot :

直接导入:PyCore::import('matplotlib.pyplot')

生成提示文件:php tools/gen-lib.php matplotlib.pyplot

也可以配置 tools/gen-all-lib.php 批量生成多个包的提示文件。

安装依赖

composer require swoole/phpy

使用 IDE 提示

require dirname(__DIR__, 2) . '/vendor/autoload.php';

$plt = python\matplotlib\pyplot::import();

$x = new PyList([1, 2, 3, 4]);

$y = new PyList([30, 20, 50, 60]);

$plt->plot($x, $y);

$plt->show();

attachments-2023-12-4F1kkNay6577f70a39c43.png

编译参数

第一个版本中我们使用了硬编码的 Python 开发目录,新版本可以使用编译参数来指定,并且底层还会自动识别 Python 版本。

现在 phpy 最低支持 Python 3.8 和 PHP 8.1,另外我们增加了一个 Dockerfile 可以参考此文件来构建 phpy 的环境。

--with-python-dir

指定 Python 的安装路径,例如 /usr/bin/python 应该设置为 --with-python-dir=/usr。

若使用 conda 安装 Python,应设置为 /opt/anaconda3

--with-python-version

指定 Python 的版本,例如 3.10、3.11、3.12,默认将使用 $python-dir/bin/python -V 来获取版本。

动态链接库问题

导入库时发生动态链接库错误,原因可能是 LD 路径错误导致,可设置环境变量指定 Python C 模块 动态库路径。

export LD_LIBRARY_PATH=/opt/anaconda3/lib

php plot.php

这种方式仅对当前的 bash 会话有效,不会影响全局,更加安全。不要直接修改 /etc/ld.so.conf.d/*.conf 增加 /opt/anaconda3/lib,这可能会导致 libc 库冲突,可能会影响操作系统其他程序的正常运行。

支持全部 Python 内置方法

在第一个版本中,我们使用了 C 代码实现了一部分内置函数,第二个版本中我们直接设置了 PyCore::__callStatic() 魔术方法,对于 PyCore 静态方法调用会自动调用 Python 的 builtins 模块对应的方法。支持了全部 Python 内置方法。

可参考 Built-in Functions 了解更多内置方法的使用

甚至我们可以使用 eval 和 exec 在 PHP 中执行 Python 代码。

$pycode = <<<PYCODE

square = {

    f'{prefix}{i}': i**2 for i in range(n)

}

PYCODE;


$globals = new PyDict([

    'n' => 10,

    'prefix' => 'square_',

]);


PyCore::exec($pycode, $globals);

$this->assertEquals(64, $globals['square']['square_8']);

$this->assertEquals(16, $globals['square']['square_4']);

迭代器支持

现在可以使用迭代器来遍历 Python 对象,可以完美支持 Python 的 yield 生成器语法。

$iter = PyCore::iter($uname);

$this->assertTrue($iter instanceof PyIter);

$list = [];

while ($next = PyCore::next($iter)) {

    $list[] = PyCore::scalar($next);

}

PHP 单元测试

phpy 项目拥有完整的单测覆盖来保证稳定性,安装成功后可使用 composer test 或者 phpunit脚本进行测试。

attachments-2023-12-gzgXhG2W6577f6f91d6a9.png

Python 单元测试

phpy使用了pytest工具编写了Python调用PHP API的用例。可使用 pytest -v tests/ 来测试模块可用性。

attachments-2023-12-QwZ4CNZA6577f6ebcdddd.png

更多例子

Numpy 科学计算

$np = PyCore::import('numpy');

$rs = $np->floor($np->random->random([3, 4])->__mul__(10));

PyCore::print($rs);

attachments-2023-12-X0QOQ9Dz6577f6dedf16f.png

matplotlib.pyplot 数学绘图

$plt = PyCore::import('matplotlib.pyplot');


$x = new PyList([1, 2, 3, 4]);

$y = new PyList([30, 20, 50, 60]);

$plt->plot($x, $y);

$plt->show();

attachments-2023-12-uVbSa5oP6577f6d19a241.jpg

更多相关技术内容咨询欢迎前往并持续关注六星社区了解详情。

想高效系统的学习Python编程语言,推荐大家关注一个微信公众号:Python编程学习圈。每天分享行业资讯、技术干货供大家阅读,关注即可免费领取整套Python入门到进阶的学习资料以及教程,感兴趣的小伙伴赶紧行动起来吧。

attachments-2022-05-rLS4AIF8628ee5f3b7e12.jpg

  • 发表于 2023-12-12 14:03
  • 阅读 ( 193 )
  • 分类:Python开发

你可能感兴趣的文章

相关问题

0 条评论

请先 登录 后评论
王昭君
王昭君

209 篇文章

作家榜 »

  1. 轩辕小不懂 2403 文章
  2. 小柒 1300 文章
  3. Pack 1135 文章
  4. Nen 576 文章
  5. 王昭君 209 文章
  6. 文双 71 文章
  7. 小威 64 文章
  8. Cara 36 文章