前言

前面簡單地嘗試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來看看內部的結構。

單個字段

測試代碼如下

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

在Rust 代碼中編寫 Python 是種怎樣的體驗_#python

 在LLDB裏面

在Rust 代碼中編寫 Python 是種怎樣的體驗_Python_02

首先,先看看foo_bound的結構

在Rust 代碼中編寫 Python 是種怎樣的體驗_#數據庫_03

再看一下Bound的定義

#[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$<enum2$<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$<enum2$<pyo3::internal::state::AttachGuard> > >) &__0 = 0x000000a2432feca8

看一下內存視圖,如下

在Rust 代碼中編寫 Python 是種怎樣的體驗_Python_04

可以發現是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,這其實也是比較麻煩的,筆者找了許久才找到

在Rust 代碼中編寫 Python 是種怎樣的體驗_Python_05

這個指針內部的結構是ob_refcnt和ob_type,

顯然是ob_type,而ob_type裏面也有非常多的東西,筆者沒找到

在Rust 代碼中編寫 Python 是種怎樣的體驗_Python_06

而這個tb_name裏面好像是這個Pyobject的名字,不是字段name的結果

在Rust 代碼中編寫 Python 是種怎樣的體驗_#rust_07

筆者沒找不到,重新考慮一下


============過了不知道多久===========


,筆者,終於知道,怎麼搞了,中間過程不必細説。總之,先打個斷點

筆者修改一下name的值

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了,結果如下

在Rust 代碼中編寫 Python 是種怎樣的體驗_f5_08

沒問題。

或者使用

(lldb) memory read -s 1 -c 5 -f c 0x0000021db01fcc10
0x21db01fcc10: hello

(lldb) x/s 0x0000021db01fcc10
0x21db01fcc10:"hello....."

都行。

多個字段的數據1

筆者修改一下測試


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

再來測試

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的數據

在Rust 代碼中編寫 Python 是種怎樣的體驗_#python_09

直入主題,複製地址,看看

(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的內容

在Rust 代碼中編寫 Python 是種怎樣的體驗_f5_10

複製地址——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

在Rust 代碼中編寫 Python 是種怎樣的體驗_#數據庫_11

0x000001af9539a9a0和a的新的地址0x1af9539a9a0一樣,那麼顯然,這個就是指向a的指針

在Rust 代碼中編寫 Python 是種怎樣的體驗_#數據庫_12

0x0000000100000138 取00000138就是312,312的類型是u32,沒問題。

有點意思。

PyList

筆者換一個類型來測試一下,PyList,代碼如下

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進行分析

在Rust 代碼中編寫 Python 是種怎樣的體驗_f5_13

地址是0x24c6b2eb1c0,看看

(lldb) x/4xg 0x24c6b2eb1c0
0x24c6b2eb1c0: 0x0000000000000001 0x00007fffde2323e0
0x24c6b2eb1d0: 0x0000000000000003 0x0000024c6b20b590

直言的説,0x0000000000000003 就是長度,而0x0000024c6b20b590就是指向vec

換一個長度試試

let a=PyList::new(py, vec![1,3,9,27,45]);

再次測試,長度應該是5,確定地址

在Rust 代碼中編寫 Python 是種怎樣的體驗_Python_14

(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

在Rust 代碼中編寫 Python 是種怎樣的體驗_Python_15

地址是0x1a8cf03af50,筆者現在已經可以斷言,裏面有0x1a8cf11b1c0這個a的地址,

結果如下

(lldb) x/4xg 0x1a8cf03af50
0x1a8cf03af50: 0x0000000000000001 0x000001a8cf2e41d0
0x1a8cf03af60: 0x000001a8cf11b1c0 0x0000000000000000

沒問題,哈哈哈哈哈哈哈哈哈哈


=========就這樣,有點意思,明天再説============


PyDict

筆者再換一個類型看看

測試代碼如下

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

可以看看函數簽名,如下。

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裏面

#[doc(alias = "PyDict")]
pub trait PyDictMethods<'py>: crate::sealed::Sealed

有很多,比如set_item、get_item、keys、values之類的,python裏面也有。

要獲取字典裏面的值,這就值得考慮了,必然涉及hash表了


===========過了不知道多久=========


修改一下a的set_item

a.set_item("id",1387).unwrap();
            a.set_item("names","hellosasafsdf").unwrap();
            a.set_item("tt6","gsdfjjuihmjhg").unwrap();

筆者直接打個斷點。直接看a的地址,如下

在Rust 代碼中編寫 Python 是種怎樣的體驗_#rust_16

0x1854bcb19c0

讀取這個地址

(lldb) x/6xg 0x1854bcb19c0
0x1854bcb19c0: 0x0000000000000001 0x00007fffec818e60
0x1854bcb19d0: 0x0000000000000003 0x0000000003323000
0x1854bcb19e0: 0x000001854ba33d30 0x0000000000000000

 

這6個東西,可能是地址,可能是數據,

可以斷言0x0000000000000003 是指的dict中key的數量。

筆者測試過,添加一個就變成了4

0x0000000003323000不可以使用,如下

在Rust 代碼中編寫 Python 是種怎樣的體驗_#rust_17

那麼就只要0x000001854ba33d30 這個地址可以使用

讀取0x000001854ba33d30

(lldb) x/6xg 0x000001854ba33d30
0x1854ba33d30: 0x0000000000000001 0x0000000000010303
0x1854ba33d40: 0x0000000000000002 0x0000000000000003
0x1854ba33d50: 0x01ffffffffff0002 0x000001854bcfa9a0

這裏也有6個地址,説實話,也只要0x000001854bcfa9a0這個地址可以使用,其他0x0000000000000002 、顯然指的數據2,不知道是什麼含義,但可以確定不是地址

總之,就只要0x000001854bcfa9a0可以使用

0x01ffffffffff0002 也不行,如下

在Rust 代碼中編寫 Python 是種怎樣的體驗_#rust_18

都在報錯

讀取0x000001854bcfa9a0

(lldb) x/6xg 0x000001854bcfa9a0
0x1854bcfa9a0: 0x0000000000000001 0x00007fffec8270c0
0x1854bcfa9b0: 0x0000000000000002 0x0a5d181c41bb99f9
0x1854bcfa9c0: 0x000001854ba37064 0x000001854b006469

這6個東西的,筆者不好説,直接看內存視圖,如下

在Rust 代碼中編寫 Python 是種怎樣的體驗_#數據庫_19

筆者專門標註了一個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

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

0x36 → 6  
0x74 → t 
0x74 → t

第三個key

第六個地址

(lldb) x/7xg 0x000001f31db81ab0
0x1f31db81ab0: 0x0000000000000001 0x00007fffec8270c0
0x1f31db81ac0: 0x000000000000000d 0xffffffffffffffff
0x1f31db81ad0: 0x0000000000000064 0x69756a6a66647367
0x1f31db81ae0: 0x00000067686a6d68

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



繼續,前面是獲取a裏面的數據,還有foo_vec,這感覺不是很需要,因為筆者可以確定foo_vec中有a的地址

首先,a的地址是

0x21df98919c0

看看foo_vec的地址

在Rust 代碼中編寫 Python 是種怎樣的體驗_#rust_20

讀取地址,結果如下

(lldb) x/4xg 0x21df98d0e70
0x21df98d0e70: 0x0000000000000001 0x0000021df9776390
0x21df98d0e80: 0x0000021df98919c0 0x0000000000000000

0x0000021df98919c0 就是a的地址,簡單。

總結

筆者回看了一下,全都是地址,確實不好看,而且地址每次運行都不一樣

只可意會,不可言傳了。

哈哈哈哈哈哈哈哈哈哈哈哈哈哈

在Rust 代碼中編寫 Python 是種怎樣的體驗_f5_21