上周我们发布了 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
测试为基准:
脚本名称 | 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
运行过程中触发的异常。
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();
编译参数
第一个版本中我们使用了硬编码的 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
脚本进行测试。
Python 单元测试
phpy
使用了pytest
工具编写了Python
调用PHP API
的用例。可使用 pytest -v tests/
来测试模块可用性。
更多例子
Numpy 科学计算
$np = PyCore::import('numpy');
$rs = $np\->floor($np\->random->random(\[3, 4\])->\_\_mul\_\_(10));
PyCore::print($rs);
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();
以上就是“phpy:连接 PHP 与 Python 生态”的全部内容,希望对你有所帮助。
关于Python技术储备
学好 Python 不论是就业还是做副业赚钱都不错,但要学会 Python 还是要有一个学习规划。最后大家分享一份全套的 Python 学习资料,给那些想学习 Python 的小伙伴们一点帮助!
一、Python所有方向的学习路线
Python所有方向的技术点做的整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。
二、Python必备开发工具
三、Python视频合集
观看零基础学习视频,看视频学习是最快捷也是最有效果的方式,跟着视频中老师的思路,从基础到深入,还是很容易入门的。
四、实战案例
光学理论是没用的,要学会跟着一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。
五、Python练习题
检查学习结果。
六、面试资料
我们学习Python必然是为了找到高薪的工作,下面这些面试题是来自阿里、腾讯、字节等一线互联网大厂最新的面试资料,并且有阿里大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。
最后祝大家天天进步!!
上面这份完整版的Python全套学习资料已经上传至CSDN官方,朋友如果需要可以直接微信扫描下方CSDN官方认证二维码免费领取【保证100%免费】。