前言
前面简单地尝试pyo3,继续尝试。
在正式搞事情之前,笔者发现一个问题,如果笔者使用Pycharm打开项目,项目没什么问题,但是笔者使用RustRover打开项目,居然会报错
error: failed to run custom build command for `pyo3-build-config v0.27.1`
note: To improve backtraces for build dependencies, set the CARGO_PROFILE_DEV_BUILD_OVERRIDE_DEBUG=true environment variable to enable debug information generation.
Caused by:
process didn't exit successfully: `F:\code\Python\rye-rp\target\debug\build\pyo3-build-config-e55f551bc4a0cfcc\build-script-build` (exit code: 1)
--- stdout
cargo:rerun-if-env-changed=PYO3_CONFIG_FILE
cargo:rerun-if-env-changed=PYO3_NO_PYTHON
cargo:rerun-if-env-changed=PYO3_ENVIRONMENT_SIGNATURE
cargo:rerun-if-env-changed=PYO3_PYTHON
cargo:rerun-if-env-changed=VIRTUAL_ENV
cargo:rerun-if-env-changed=CONDA_PREFIX
cargo:rerun-if-env-changed=PATH
--- stderr
error: no Python 3.x interpreter found
意思是,没有环境变量PYO3_PYTHON,
笔者在根目录下新建一个.cargo目录,其中新建一个config.toml文件
内容如下
[env]
PYO3_PYTHON = "F:/code/Python/rye-rp/.venv/Scripts/python.exe"
笔者设置为环境变量为虚拟环境里面的环境变量。行
笔者决定使用lldb来看看内部的结构。
正文
单个字段
测试代码如下
rust
use pyo3::prelude::*;
use std::any::type_name_of_val;
use pyo3::types::PyString;
#[pyclass]
struct Foo{
#[pyo3(get, set)]
name: String
}
#[pymethods]
impl Foo{
#[new]
fn new(name: String) -> Self {
Self{
name
}
}
}
#[cfg(test)]
mod tests{
use super::*;
use pyo3::Python;
#[test]
fn test_get_name(){
Python::initialize();
Python::attach(|py|{
let a=PyString::new(py,"ni hao");
let foo_bound=Bound::new(py,Foo::new(
"hello world".to_string(),
)).unwrap();
println!(" 类型: {}", type_name_of_val(&foo_bound));
})
}
}
那么可以打个断点,看看foo_bound

在LLDB里面

首先,先看看foo_bound的结构

再看一下Bound的定义
rust
#[repr(transparent)]
pub struct Bound<'py, T>(Python<'py>, ManuallyDrop<Py<T>>);
首先,Python是一个证明,里面是PhantomData,而PhantomData在在运行时 完全不存在,里面是空的,可以验证一下里面的内容
如下
(lldb) expr foo_bound.0.__0
(core::marker::PhantomData<ref\<pyo3::internal::state::AttachGuard> > >) __0 = {}
(lldb) expr foo_bound.0.__1
(core::marker::PhantomData<pyo3::marker::NotSend>) __1 = {}
笔者查看里面的内容,可以发现两个都是{},都是空的,没有结构,没有数据
可以获取地址,然后查看一下内存视图,加上符号&------取地址
如下
(lldb) expr &foo_bound.0.__0
(*mut core::marker::PhantomData<ref\<pyo3::internal::state::AttachGuard> > >) &__0 = 0x000000a2432feca8
看一下内存视图,如下

可以发现是00 00 00 00 00 00 00 00,足以证明内容是空的,什么都没有。
而前面e0 ae 34 b2 ab 01 00 00,这是什么,可以输出一下food_bound的第二个内容的指针,即
(lldb) expr foo_bound.1.value.0.pointer
(*mut pyo3_ffi::object::PyObject) pointer = 0x000001abb234aee0
Windows(x86/x64)是小端序,所以是0x000001abb234aee0
对于0x000001abb234aee0, 可以发现这就是指针,指向PyObject。
**可以尝试读取一下name,**这其实也是比较麻烦的,笔者找了许久才找到

这个指针内部的结构是ob_refcnt和ob_type,
显然是ob_type,而ob_type里面也有非常多的东西,笔者没找到

而这个tb_name里面好像是这个Pyobject的名字,不是字段name的结果
笔者没找不到,重新考虑一下
============过了不知道多久===========
,笔者,终于知道,怎么搞了,中间过程不必细说。总之,先打个断点
笔者修改一下name的值
rust
let foo_bound=Bound::new(py,Foo::new("hello".to_string())).unwrap();
变成hello
获取指针
(lldb) expr foo_bound.1.value.0.pointer
(*mut pyo3_ffi::object::PyObject) pointer = 0x0000021db068aee0
(lldb) x/4xg 0x0000021db068aee0
0x21db068aee0: 0x0000000000000001 0x0000021db05235a0
0x21db068aef0: 0x0000000000000005 0x0000021db01fcc10
注意到0x0000000000000005 ,这个显示就是长度了,hello长度为5,那么0x0000021db01fcc10应该就是hello了,结果如下
没问题。
或者使用
(lldb) memory read -s 1 -c 5 -f c 0x0000021db01fcc10
0x21db01fcc10: hello
(lldb) x/s 0x0000021db01fcc10
0x21db01fcc10:"hello....."
都行。
多个字段的数据1
笔者修改一下测试
rust
use pyo3::prelude::*;
use std::any::type_name_of_val;
use pyo3::types::PyString;
#[pyclass]
struct Foo{
#[pyo3(get, set)]
name: String,
#[pyo3(get, set)]
age:u32,
#[pyo3(get, set)]
t:String,
}
#[pymethods]
impl Foo{
#[new]
fn new(name: String,age:u32,t:String) -> Self {
Self{
name,
age,
t
}
}
}
#[cfg(test)]
mod tests{
use super::*;
use pyo3::Python;
#[test]
fn test_get_name(){
Python::initialize();
Python::attach(|py|{
let a:u32=15;
let foo_bound=Bound::new(py,Foo::new(
"hello world".to_string(),
96,
"asdasdas".to_string()
)).unwrap();
println!(" 类型: {}", type_name_of_val(&foo_bound));
})
}
}
在println哪里打个断点,看看
首先确定地址
(lldb) x/4xg 0x257977a3780
0x257977a3780: 0x0000000000000001 0x00000257976935a0
0x257977a3790: 0x000000000000000b 0x000002579769bbb0
0x000000000000000b可以确定就是指长度11,0x000002579769bbb0应该就是内容
(lldb) x/s 0x000002579769bbb0
0x2579769bbb0: "hello world\xab\xab\x
确实如此,
(lldb) x/10xg 0x257977a3780
0x257977a3780: 0x0000000000000001 0x00000257976935a0
0x257977a3790: 0x000000000000000b 0x000002579769bbb0
0x257977a37a0: 0x000000000000000b 0x0000000000000008
0x257977a37b0: 0x000002579740ccf0 0x0000000000000008
0x257977a37c0: 0x0000025700000060 0x0000000000000000
结合代码。仔细看看,
0x000002579769bbb0表示hello world ,
0x000000000000000b 这个不知道是什么东西,
0x0000000000000008显示是第二个字符串的长度,0x000002579740ccf0应该是内容
(lldb) x/s 0x000002579740ccf0
0x2579740ccf0: "asdasdas\xab\xab
确实如此,
后面0x0000000000000008,表示8,不知道什么意思
后面 0x0000025700000060 ,也不知道是什么意思,
后面就没有了,笔者看了老半天,笔者好像看明白了
首先,96的16进制是60,而有一个00000060,是60,所以,笔者猜测这个就是96所在的位置
笔者换一个数字312,再来看看
(lldb) x/10xg 0x17242f6d020
0x17242f6d020: 0x0000000000000001 0x0000017242e331a0
0x17242f6d030: 0x000000000000000b 0x0000017242e66220
0x17242f6d040: 0x000000000000000b 0x0000000000000008
0x17242f6d050: 0x0000017242e3bbb0 0x0000000000000008
0x17242f6d060: 0x0000017200000138 0x0000000000000000
首先312的16进制是138,直接看关键东西,0x0000017200000138里面的00000138,没问题
u32表示32位,确实,只有32位,笔者明白了
如果笔者改成u64,就是64位了,如下
(lldb) x/10xg 0x1ef55f03780
0x1ef55f03780: 0x0000000000000001 0x000001ef55e2ee80
0x1ef55f03790: 0x000000000000000b 0x000001ef55b0ccf0
0x1ef55f037a0: 0x000000000000000b 0x0000000000000008
0x1ef55f037b0: 0x000001ef55e075e0 0x0000000000000008
0x1ef55f037c0: 0x0000000000000138 0x0000000000000000
没问题
多个字段的数据2
再来测试
rust
use pyo3::prelude::*;
use std::any::type_name_of_val;
use pyo3::types::PyString;
#[pyclass]
struct Foo{
#[pyo3(get, set)]
name: String,
#[pyo3(get, set)]
age:u32,
#[pyo3(get, set)]
g:Py<PyString>
}
#[pymethods]
impl Foo{
#[new]
fn new(name: String,age:u32,g:Bound<PyString>) -> Self {
Self{
name,
age,
g:g.unbind()
}
}
}
#[cfg(test)]
mod tests{
use super::*;
use pyo3::Python;
#[test]
fn test_get_name(){
Python::initialize();
Python::attach(|py|{
let a=PyString::new(py,"ni hao");
let foo_bound=Bound::new(py,Foo::new(
"hello world".to_string(),
312,
a
)).unwrap();
println!(" 类型: {}", type_name_of_val(&foo_bound));
})
}
}
断点,运行 首先,先看看a的数据

直入主题,复制地址,看看
(lldb) x/6xg 0x180014da9a0
0x180014da9a0: 0x0000000000000001 0x00007fffda0170c0
0x180014da9b0: 0x0000000000000006 0xffffffffffffffff
0x180014da9c0: 0x0000018001277064 0x00006f616820696e
0x0000000000000006 显然是长度,0xffffffffffffffff和0x000001f6c01e7064不知道是干什么的
而 0x00006f616820696e是内容,
6e 是110,表示n
69 是105,表示i
其他同理。
看看foo_bound的内容

复制地址------0x1800127bbb0
这两个地址之差是0x25edf0------ 2.37MB,有点大
这个差值反映的是两个 Python 堆对象之间的内存距离。
然后,笔者重新运行一下,失误了,重新来
新的a的地址------0x1af9539a9a0
新的foo_bound的地址------0x1af9513bbb0
二者之差还是2.37MB, 可以的,查看0x1af9513bbb0
(lldb) x/8xg 0x1af9513bbb0
0x1af9513bbb0: 0x0000000000000001 0x000001af9526b620
0x1af9513bbc0: 0x000000000000000b 0x000001af95245a30
0x1af9513bbd0: 0x000000000000000b 0x000001af9539a9a0
0x1af9513bbe0: 0x0000000100000138 0x0000000000000000
看看地址,0x000000000000000b是11,指的就是hello world的长度11
那么0x000001af95245a30就是指向hello world

0x000001af9539a9a0和a的新的地址0x1af9539a9a0一样,那么显然,这个就是指向a的指针

0x0000000100000138 取00000138就是312,312的类型是u32,没问题。
有点意思。
PyList
笔者换一个类型来测试一下,PyList,代码如下
rust
use pyo3::prelude::*;
use std::any::type_name_of_val;
use pyo3::types::{PyList};
#[pyclass]
struct VecFoo{
#[pyo3(get, set)]
name: Py<PyList>,
}
#[pymethods]
impl VecFoo{
#[new]
fn new(name:Bound<PyList>) -> Self {
Self{
name:name.unbind()
}
}
}
#[cfg(test)]
mod tests{
use super::*;
use pyo3::Python;
#[test]
fn test_get_vec(){
Python::initialize();
Python::attach(|py|{
let a=PyList::new(py, vec![1,2,3]);
let foo_vec=Bound::new(py,VecFoo::new(
a.unwrap()
)).unwrap();
println!(" 类型: {}", type_name_of_val(&foo_vec));
})
}
}
打个断点,先对a进行分析

地址是0x24c6b2eb1c0,看看
(lldb) x/4xg 0x24c6b2eb1c0
0x24c6b2eb1c0: 0x0000000000000001 0x00007fffde2323e0
0x24c6b2eb1d0: 0x0000000000000003 0x0000024c6b20b590
直言的说,0x0000000000000003 就是长度,而0x0000024c6b20b590就是指向vec
换一个长度试试
rust
let a=PyList::new(py, vec![1,3,9,27,45]);
再次测试,长度应该是5,确定地址

(lldb) x/4xg 0x1a8cf11b1c0
0x1a8cf11b1c0: 0x0000000000000001 0x00007fffde7123e0
0x1a8cf11b1d0: 0x0000000000000005 0x000001a8cf3ea9a0
没问题,看看0x000001a8cf3ea9a0
(lldb) x/6xg 0x000001a8cf3ea9a0
0x1a8cf3ea9a0: 0x00007fffde7f43a8 0x00007fffde7f43e8
0x1a8cf3ea9b0: 0x00007fffde7f44a8 0x00007fffde7f46e8
0x1a8cf3ea9c0: 0x00007fffde7f4928 0x000001a8cf0e71e0
可以发现前5个地址之间相差0x40,64个字节 。
这就很显然了,数字必然在里面。看最后一个
(lldb) x/4xg 0x00007fffde7f4928
0x7fffde7f4928: 0x00000000ffffffff 0x00007fffde712580
0x7fffde7f4938: 0x0000000000000008 0x000000000000002d
0x000000000000002d是 **45,**没问题。
看foo_vec

地址是0x1a8cf03af50 ,笔者现在已经可以断言,里面有0x1a8cf11b1c0这个a的地址,
结果如下
(lldb) x/4xg 0x1a8cf03af50
0x1a8cf03af50: 0x0000000000000001 0x000001a8cf2e41d0
0x1a8cf03af60: 0x000001a8cf11b1c0 0x0000000000000000
没问题,哈哈哈哈哈哈哈哈哈哈
=========就这样,有点意思,明天再说============
PyDict
笔者再换一个类型看看
测试代码如下
rust
use pyo3::prelude::*;
use std::any::type_name_of_val;
use pyo3::types::{PyDict};
#[pyclass]
struct VecFoo{
#[pyo3(get, set)]
name: Py<PyDict>,
}
#[pymethods]
impl VecFoo{
#[new]
fn new(name:Bound<PyDict>) -> Self {
Self{
name:name.unbind()
}
}
}
#[cfg(test)]
mod tests{
use super::*;
use pyo3::Python;
#[test]
fn test_get_vec(){
Python::initialize();
Python::attach(|py|{
let a=PyDict::new(py);
a.set_item("id",1).unwrap();
a.set_item("name","hello").unwrap();
let foo_vec=Bound::new(py,VecFoo::new(
a
)).unwrap();
println!(" 类型: {}", type_name_of_val(&foo_vec));
})
}
}
无论是PyDict还是PyList,调用new方法,都是返回Bound
可以看看函数签名,如下。
rust
impl PyDict {
/// Creates a new empty dictionary.
pub fn new(py: Python<'_>) -> Bound<'_, PyDict> {
unsafe { ffi::PyDict_New().assume_owned(py).cast_into_unchecked() }
}
....
}
里面也是比较复杂的,总之,返回Bound。
PyDict的可以调用的方法在PyDictMethods里面
rust
#[doc(alias = "PyDict")]
pub trait PyDictMethods<'py>: crate::sealed::Sealed
有很多,比如set_item、get_item、keys、values之类的,python里面也有。
要获取字典里面的值,这就值得考虑了,必然涉及hash表了
===========过了不知道多久=========
修改一下a的set_item
rust
a.set_item("id",1387).unwrap();
a.set_item("names","hellosasafsdf").unwrap();
a.set_item("tt6","gsdfjjuihmjhg").unwrap();
笔者直接打个断点。直接看a的地址,如下

0x1854bcb19c0
读取这个地址
(lldb) x/6xg 0x1854bcb19c0
0x1854bcb19c0: 0x0000000000000001 0x00007fffec818e60
0x1854bcb19d0: 0x0000000000000003 0x0000000003323000
0x1854bcb19e0: 0x000001854ba33d30 0x0000000000000000
这6个东西,可能是地址,可能是数据,
可以断言0x0000000000000003是指的dict中key的数量。
笔者测试过,添加一个就变成了4
**0x0000000003323000不可以使用,**如下

那么就只要0x000001854ba33d30这个地址可以使用
读取0x000001854ba33d30
(lldb) x/6xg 0x000001854ba33d30
0x1854ba33d30: 0x0000000000000001 0x0000000000010303
0x1854ba33d40: 0x0000000000000002 0x0000000000000003
0x1854ba33d50: 0x01ffffffffff0002 0x000001854bcfa9a0
这里也有6个地址,说实话,也只要0x000001854bcfa9a0这个地址可以使用,其他0x0000000000000002、显然指的数据2,不知道是什么含义,但可以确定不是地址
总之,就只要0x000001854bcfa9a0可以使用
0x01ffffffffff0002也不行,如下

都在报错
读取0x000001854bcfa9a0
(lldb) x/6xg 0x000001854bcfa9a0
0x1854bcfa9a0: 0x0000000000000001 0x00007fffec8270c0
0x1854bcfa9b0: 0x0000000000000002 0x0a5d181c41bb99f9
0x1854bcfa9c0: 0x000001854ba37064 0x000001854b006469
这6个东西的,笔者不好说,直接看内存视图,如下

笔者专门标注了一个id,就是最后一个0x000001854b006469,中的6469,即 69 64
是id,这是巧合吗?
笔者不装了,直接操作。
重新运行一下。
a的地址是0x1f31db819c0
第一次读取
(lldb) x/6xg 0x1f31db819c0
0x1f31db819c0: 0x0000000000000001 0x00007fffec818e60
0x1f31db819d0: 0x0000000000000003 0x0000000003323000
0x1f31db819e0: 0x000001f31da13d30 0x0000000000000000
0x000001f31da13d30 +40=0x000001f31da13d58
第二次读取
(lldb) x/6xg 0x000001f31da13d58
0x1f31da13d58: 0x000001f31dbca9a0 0x000001f31d96b590
0x1f31da13d68: 0x000001f31dbcab20 0x000001f31db81a30
0x1f31da13d78: 0x000001f31dbcaaf0 0x000001f31db81ab0
这六个地址,分别读取
第一个地址
(lldb) x/6xg 0x000001f31dbca9a0
0x1f31dbca9a0: 0x0000000000000001 0x00007fffec8270c0
0x1f31dbca9b0: 0x0000000000000002 0x3a9079259de0afc1
0x1f31dbca9c0: 0x000001f31da17064 0x000001f31d006469
可以发现最后一个地址0x000001f31d006469,显然是id
至于前面的000001f31d00,笔者也不知道。
总之,这是第一个key
第二个地址
(lldb) x/6xg 0x000001f31d96b590
0x1f31d96b590: 0x0000000000000001 0x00007fffec822580
0x1f31d96b5a0: 0x0000000000000008 0x000001f30000056b
0x1f31d96b5b0: 0x0000000000000001 0x00007fffec822580
注意到,0x000001f30000056b中的56b,前面1387,16进制是0x56b,那么这个就是id对应的值
这是第一个value
第三个地址
(lldb) x/6xg 0x000001f31dbcab20
0x1f31dbcab20: 0x0000000000000001 0x00007fffec8270c0
0x1f31dbcab30: 0x0000000000000005 0x8dbc1a921a4da357
0x1f31dbcab40: 0xffffffffffffff64 0x00000073656d616e
注意到0x00000073656d616e,变成 6e 61 6d 65 73 变成ascii字符是n a m e s
这是第二个key
第四个地址
(lldb) x/7xg 0x000001f31db81a30
0x1f31db81a30: 0x0000000000000001 0x00007fffec8270c0
0x1f31db81a40: 0x000000000000000d 0xffffffffffffffff
0x1f31db81a50: 0x000001f31d9b2364 0x7361736f6c6c6568
0x1f31db81a60: 0x0000006664736661
注意到0x7361736f6c6c6568和0x0000006664736661
rust
73 → s
61 → a
73 → s
6f → o
6c → l
6c → l
65 → e
68 → h
66 → f
64 → d
73 → s
66 → f
61 → a
第二个value
第五个地址
(lldb) x/6xg 0x000001f31dbcaaf0
0x1f31dbcaaf0: 0x0000000000000001 0x00007fffec8270c0
0x1f31dbcab00: 0x0000000000000003 0xa2f85981c063f6ea
0x1f31dbcab10: 0xffffffffffffff64 0x0000000000367474
rust
0x36 → 6
0x74 → t
0x74 → t
第三个key
第六个地址
(lldb) x/7xg 0x000001f31db81ab0
0x1f31db81ab0: 0x0000000000000001 0x00007fffec8270c0
0x1f31db81ac0: 0x000000000000000d 0xffffffffffffffff
0x1f31db81ad0: 0x0000000000000064 0x69756a6a66647367
0x1f31db81ae0: 0x00000067686a6d68
rust
69 → i
75 → u
6a → j
6a → j
66 → f
64 → d
73 → s
67 → g
67 → g
68 → h
6a → j
6d → m
68 → h
第三个value
行,感觉没问题,没有计算什么hash值之类的,最关键的一步是+40
至于为什么是+40,这可能涉及内部Cpython的定义了,笔者使用的python313。
===========明天再说,就这样==========
继续,前面是获取a里面的数据,还有foo_vec,这感觉不是很需要,因为笔者可以确定foo_vec中有a的地址
首先,a的地址是
0x21df98919c0
看看foo_vec的地址

读取地址,结果如下
(lldb) x/4xg 0x21df98d0e70
0x21df98d0e70: 0x0000000000000001 0x0000021df9776390
0x21df98d0e80: 0x0000021df98919c0 0x0000000000000000
0x0000021df98919c0就是a的地址,简单。
总结
笔者回看了一下,全都是地址,确实不好看,而且地址每次运行都不一样
只可意会,不可言传了。
哈哈哈哈哈哈哈哈哈哈哈哈哈哈
