From 0e2afb7b8b96213104ac08cb99de97287dd4dc41 Mon Sep 17 00:00:00 2001 From: messense Date: Fri, 28 Jul 2017 22:21:59 +0800 Subject: [PATCH] Add __dict__ support (#68) --- CHANGES.txt | 2 ++ guide/src/class.md | 6 +++--- pyo3cls/src/py_class.rs | 14 +++++++++++++- src/typeob.rs | 14 ++++++++++++-- tests/test_class.rs | 26 ++++++++++++++++++++++++++ 5 files changed, 56 insertions(+), 6 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index a780238d..9a9500db 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -12,6 +12,8 @@ Changes * Added subclass support #64 +* Added `self.__dict__` supoort #68 + 0.1.0 (07-23-2017) ^^^^^^^^^^^^^^^^^^ diff --git a/guide/src/class.md b/guide/src/class.md index 68ae5906..4bdb30aa 100644 --- a/guide/src/class.md +++ b/guide/src/class.md @@ -59,9 +59,10 @@ so that they can benefit from a freelist. `XXX` is a number of items for free li participate in python garbage collector. If custom class contains references to other python object that can be collector `PyGCProtocol` trait has to be implemented. * `weakref` - adds support for python weak references -* `base=BaseType` - use custom base class. BaseType is type which is +* `base=BaseType` - use custom base class. BaseType is type which is implements `PyTypeInfo` trait. * `subclass` - adds subclass support so that Python classes can inherit from this class +* `dict` - adds `__dict__` support, the instances of this type have a dictionary containing instance variables ## Constructor @@ -127,8 +128,7 @@ parent class. ## Object properties -Instance's `__dict__` attributes is not supported by pyo3 library. But it is -possible to specify instance get/set descriptors. Descriptor methods can be defined in +Descriptor methods can be defined in `#[methods]` `impl` block only and has to be annotated with `#[getter]` or `[setter]` attributes. i.e. diff --git a/pyo3cls/src/py_class.rs b/pyo3cls/src/py_class.rs index 6442e1be..ae8f59d1 100644 --- a/pyo3cls/src/py_class.rs +++ b/pyo3cls/src/py_class.rs @@ -162,9 +162,12 @@ fn impl_class(cls: &syn::Ident, base: &syn::Ident, // insert space for weak ref let mut has_weakref = false; + let mut has_dict = false; for f in flags.iter() { if *f == syn::Ident::from("_pyo3::typeob::PY_TYPE_FLAG_WEAKREF") { has_weakref = true; + } else if *f == syn::Ident::from("_pyo3::typeob::PY_TYPE_FLAG_DICT") { + has_dict = true; } } let weakref = if has_weakref { @@ -172,6 +175,11 @@ fn impl_class(cls: &syn::Ident, base: &syn::Ident, } else { syn::Ident::from("0") }; + let dict = if has_dict { + syn::Ident::from("std::mem::size_of::<*const _pyo3::ffi::PyObject>()") + } else { + syn::Ident::from("0") + }; quote! { impl _pyo3::typeob::PyTypeInfo for #cls { @@ -184,7 +192,7 @@ fn impl_class(cls: &syn::Ident, base: &syn::Ident, const SIZE: usize = { Self::OFFSET as usize + - std::mem::size_of::<#cls>() + #weakref + std::mem::size_of::<#cls>() + #weakref + #dict }; const OFFSET: isize = { // round base_size up to next multiple of align @@ -384,6 +392,10 @@ fn parse_attribute(attr: String) -> (HashMap<&'static str, syn::Ident>, flags.push(syn::Ident::from("_pyo3::typeob::PY_TYPE_FLAG_BASETYPE")); continue } + "dict" => { + flags.push(syn::Ident::from("_pyo3::typeob::PY_TYPE_FLAG_DICT")); + continue + } _ => { println!("Unsupported parameter: {:?}", key); } diff --git a/src/typeob.rs b/src/typeob.rs index 40e0a5dd..55ac63f1 100644 --- a/src/typeob.rs +++ b/src/typeob.rs @@ -55,6 +55,9 @@ pub const PY_TYPE_FLAG_WEAKREF: usize = 1<<1; /// Type object can be used as the base type of another type pub const PY_TYPE_FLAG_BASETYPE: usize = 1<<2; +/// The instances of this type have a dictionary containing instance variables +pub const PY_TYPE_FLAG_DICT: usize = 1<<3; + impl<'a, T: ?Sized> PyTypeInfo for &'a T where T: PyTypeInfo { type Type = T::Type; @@ -208,10 +211,17 @@ pub fn initialize_type<'p, T>(py: Python<'p>, module_name: Option<&str>) -> PyRe // type size type_object.tp_basicsize = ::SIZE as ffi::Py_ssize_t; + let mut offset = T::SIZE; // weakref support (check py3cls::py_class::impl_class) if T::FLAGS & PY_TYPE_FLAG_WEAKREF != 0 { - type_object.tp_weaklistoffset = - (T::SIZE - std::mem::size_of::<*const ffi::PyObject>()) as isize; + offset -= std::mem::size_of::<*const ffi::PyObject>(); + type_object.tp_weaklistoffset = offset as isize; + } + + // __dict__ support + if T::FLAGS & PY_TYPE_FLAG_DICT != 0 { + offset -= std::mem::size_of::<*const ffi::PyObject>(); + type_object.tp_dictoffset = offset as isize; } // GC support diff --git a/tests/test_class.rs b/tests/test_class.rs index f6cb29f3..96f460ef 100644 --- a/tests/test_class.rs +++ b/tests/test_class.rs @@ -1254,3 +1254,29 @@ fn subclass() { .map_err(|e| e.print(py)) .unwrap(); } + +#[py::class(dict)] +struct DunderDictSupport { + token: PyToken, +} + +#[test] +fn dunder_dict_support() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let inst = Py::new_ref(py, |t| DunderDictSupport{token: t}).unwrap(); + py_run!(py, inst, "inst.a = 1; assert inst.a == 1"); +} + +#[py::class(weakref, dict)] +struct WeakRefDunderDictSupport { + token: PyToken, +} + +#[test] +fn weakref_dunder_dict_support() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let inst = Py::new_ref(py, |t| WeakRefDunderDictSupport{token: t}).unwrap(); + py_run!(py, inst, "import weakref; assert weakref.ref(inst)() is inst; inst.a = 1; assert inst.a == 1"); +}