首页
下载
文档
问答社区
视频
捐赠
源代码
AI 助理
赞助商
CRMEB
Apipost
腾讯云
微擎
禅道
51Talk
商业产品
Swoole AI 智能文档翻译器
Swoole-Compiler PHP 代码加密器
CRMEB 新零售社交电商系统
登录
注册
全部
提问
分享
讨论
建议
公告
开发框架
CodeGalaxy
发表新帖
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` 下也能常驻内存。 ```php $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` 以上内存空间才可以运行 > 测试代码请参考 [压力测试](https://github.com/swoole/phpy/blob/main/docs/benchmark.md) ## 结果对比 ```shell (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` 测试为基准: | 脚本名称 | Set | Get | |:----------|:----:|-----:| | dict.php | 114% | 125% | | array.php | 337% | 599% | - `phpy` 以 `PHP` 代码写入 `PyDict` 的性能比原生 `Python` 高 `14%`,读取性能高 `25%` - `PHP` 写入 `PHP Array` 的性能比 `Python 写入 Dict` 高 `237%`,读取高出了近 `500%` # 异常捕获 最新版本支持了 `Python` 和 `PHP` 异常的融合,可以在 `PHP` 代码中捕获 `Python` 运行过程中触发的异常。 ```php 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` 自动提示文件。使用方法: ```shell 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` 批量生成多个包的提示文件。 ## 安装依赖 ```shell composer require swoole/phpy ``` ## 使用 IDE 提示 ```php 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(); ``` ![](https://wenda-1252906962.file.myqcloud.com/uploads/202312/1_6576de45b9eef.png) # 编译参数 第一个版本中我们使用了硬编码的 `Python` 开发目录,新版本可以使用编译参数来指定,并且底层还会自动识别 `Python` 版本。 现在 `phpy` 最低支持 `Python 3.8` 和 `PHP 8.1`,另外我们增加了一个 [Dockerfile](https://github.com/swoole/phpy/blob/main/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 模块` 动态库路径。 ```shell 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](https://docs.python.org/3/library/functions.html) 了解更多内置方法的使用 甚至我们可以使用 `eval` 和 `exec` 在 `PHP` 中执行 `Python` 代码。 ```php $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` 生成器语法。 ```php $iter = PyCore::iter($uname); $this->assertTrue($iter instanceof PyIter); $list = []; while ($next = PyCore::next($iter)) { $list[] = PyCore::scalar($next); } ``` # PHP 单元测试 `phpy` 项目拥有完整的单测覆盖来保证稳定性,安装成功后可使用 `composer test` 或者 `phpunit`脚本进行测试。 ![](https://wenda-1252906962.file.myqcloud.com/uploads/202312/1_6576e2257dfe3.png) # Python 单元测试 `phpy`使用了`pytest`工具编写了`Python`调用`PHP API`的用例。可使用 `pytest -v tests/` 来测试模块可用性。 ![](https://wenda-1252906962.file.myqcloud.com/uploads/202312/1_6576e29659c4c.png) # 更多例子 ## Numpy 科学计算 ```php $np = PyCore::import('numpy'); $rs = $np->floor($np->random->random([3, 4])->__mul__(10)); PyCore::print($rs); ``` ![](https://wenda-1252906962.file.myqcloud.com/uploads/202312/1_6576e2d9022a0.png) ## matplotlib.pyplot 数学绘图 ```php $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(); ``` ![](https://wenda-1252906962.file.myqcloud.com/uploads/202312/1_6576e353b8d91.png) # 微信交流群 ![](https://wenda-1252906962.file.myqcloud.com/uploads/202312/1_6576e5353d6c7.png)
发布于11月前 · 45 次浏览 · 来自
分享
Rango
上周我们发布了 `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` 下也能常驻内存。 ```php $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` 以上内存空间才可以运行 > 测试代码请参考 [压力测试](https://github.com/swoole/phpy/blob/main/docs/benchmark.md) ## 结果对比 ```shell (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` 测试为基准: | 脚本名称 | Set | Get | |:----------|:----:|-----:| | dict.php | 114% | 125% | | array.php | 337% | 599% | - `phpy` 以 `PHP` 代码写入 `PyDict` 的性能比原生 `Python` 高 `14%`,读取性能高 `25%` - `PHP` 写入 `PHP Array` 的性能比 `Python 写入 Dict` 高 `237%`,读取高出了近 `500%` # 异常捕获 最新版本支持了 `Python` 和 `PHP` 异常的融合,可以在 `PHP` 代码中捕获 `Python` 运行过程中触发的异常。 ```php 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` 自动提示文件。使用方法: ```shell 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` 批量生成多个包的提示文件。 ## 安装依赖 ```shell composer require swoole/phpy ``` ## 使用 IDE 提示 ```php 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(); ``` ![](https://wenda-1252906962.file.myqcloud.com/uploads/202312/1_6576de45b9eef.png) # 编译参数 第一个版本中我们使用了硬编码的 `Python` 开发目录,新版本可以使用编译参数来指定,并且底层还会自动识别 `Python` 版本。 现在 `phpy` 最低支持 `Python 3.8` 和 `PHP 8.1`,另外我们增加了一个 [Dockerfile](https://github.com/swoole/phpy/blob/main/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 模块` 动态库路径。 ```shell 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](https://docs.python.org/3/library/functions.html) 了解更多内置方法的使用 甚至我们可以使用 `eval` 和 `exec` 在 `PHP` 中执行 `Python` 代码。 ```php $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` 生成器语法。 ```php $iter = PyCore::iter($uname); $this->assertTrue($iter instanceof PyIter); $list = []; while ($next = PyCore::next($iter)) { $list[] = PyCore::scalar($next); } ``` # PHP 单元测试 `phpy` 项目拥有完整的单测覆盖来保证稳定性,安装成功后可使用 `composer test` 或者 `phpunit`脚本进行测试。 ![](https://wenda-1252906962.file.myqcloud.com/uploads/202312/1_6576e2257dfe3.png) # Python 单元测试 `phpy`使用了`pytest`工具编写了`Python`调用`PHP API`的用例。可使用 `pytest -v tests/` 来测试模块可用性。 ![](https://wenda-1252906962.file.myqcloud.com/uploads/202312/1_6576e29659c4c.png) # 更多例子 ## Numpy 科学计算 ```php $np = PyCore::import('numpy'); $rs = $np->floor($np->random->random([3, 4])->__mul__(10)); PyCore::print($rs); ``` ![](https://wenda-1252906962.file.myqcloud.com/uploads/202312/1_6576e2d9022a0.png) ## matplotlib.pyplot 数学绘图 ```php $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(); ``` ![](https://wenda-1252906962.file.myqcloud.com/uploads/202312/1_6576e353b8d91.png) # 微信交流群 ![](https://wenda-1252906962.file.myqcloud.com/uploads/202312/1_6576e5353d6c7.png)
赞
0
分享
收藏
提问
分享
讨论
建议
公告
开发框架
CodeGalaxy
评论
还没有评论!
微信公众号
热门内容
作者其它话题
- CodeGalaxy K3s 轻量集群节点之间如何实现负载均衡
- 有没有办法判断当前是否运行在swoole守护进程里面?
暂无回复的问答
- CodeGalaxy K3s 轻量集群节点之间如何实现负载均衡
- 关于openssl CURL WARNING swSSL_connect: SSL_connect(fd=69) failed. Error: error:141A318A:SSL routines:tls_process_ske_dhe:dh key too small[1|394]
- 多个模型如何进行事务异常回退?
- websocket开启wss报错
- 协程tcp服务器如何使用多进程?recv()方法接收信息,打印出来的pid一直是同一个。没用使用到多进程啊。