Skip to content

Commit

Permalink
add option to override tp_new to handle PyBaseObject_Type
Browse files Browse the repository at this point in the history
  • Loading branch information
mbway committed Dec 14, 2024
1 parent 79ba367 commit c5e2f75
Show file tree
Hide file tree
Showing 4 changed files with 37 additions and 29 deletions.
5 changes: 2 additions & 3 deletions src/impl_/pyclass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1138,9 +1138,8 @@ pub trait PyClassBaseType: Sized {
type BaseNativeType;
type Initializer: PyObjectInit<Self>;
type PyClassMutability: PyClassMutability;
/// Whether `__new__` ([ffi::PyTypeObject::tp_new]) for this type accepts arguments other
/// than the type of object to create.
const NEW_ACCEPTS_ARGUMENTS: bool = true;
/// Use the provided function instead of [ffi::PyTypeObject::tp_new] when creating an object of this type
const OVERRIDE_TP_NEW: Option<ffi::newfunc> = None;
}

/// Implementation of tp_dealloc for pyclasses without gc
Expand Down
43 changes: 22 additions & 21 deletions src/impl_/pyclass_init.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
//! Contains initialization utilities for `#[pyclass]`.
use crate::exceptions::PyTypeError;
use crate::ffi_ptr_ext::FfiPtrExt;
use crate::internal::get_slot::TP_NEW;
use crate::types::{PyDict, PyTuple, PyType};
use crate::{ffi, Borrowed, Bound, PyErr, PyResult, Python};
use crate::{ffi, Bound, PyErr, PyResult, Python};
use crate::{ffi::PyTypeObject, sealed::Sealed, type_object::PyTypeInfo};
use std::marker::PhantomData;

Expand Down Expand Up @@ -46,29 +47,29 @@ impl<T: PyTypeInfo + PyClassBaseType> PyObjectInit<T> for PyNativeTypeInitialize
subtype: *mut PyTypeObject,
args: &Bound<'_, PyTuple>,
kwargs: Option<&Bound<'_, PyDict>>,
new_accepts_arguments: bool,
override_tp_new: Option<ffi::newfunc>,
) -> PyResult<*mut ffi::PyObject> {
let native_base_type_borrowed: Borrowed<'_, '_, PyType> = native_base_type
.cast::<ffi::PyObject>()
.assume_borrowed_unchecked(py)
.downcast_unchecked();
let tp_new = native_base_type_borrowed
.get_slot(TP_NEW)
.unwrap_or(ffi::PyType_GenericNew);

let obj = if new_accepts_arguments {
tp_new(
subtype,
args.as_ptr(),
kwargs
.map(|obj| obj.as_ptr())
.unwrap_or(std::ptr::null_mut()),
)
let tp_new = if let Some(tp_new) = override_tp_new {
tp_new
} else {
let args = PyTuple::empty(py);
tp_new(subtype, args.as_ptr(), std::ptr::null_mut())
native_base_type
.cast::<ffi::PyObject>()
.assume_borrowed_unchecked(py)
.downcast_unchecked::<PyType>()
.get_slot(TP_NEW)
.ok_or_else(|| {
PyTypeError::new_err("cannot construct type that does not define __new__")
})?
};

let obj = tp_new(
subtype,
args.as_ptr(),
kwargs
.map(|obj| obj.as_ptr())
.unwrap_or(std::ptr::null_mut()),
);

if obj.is_null() {
Err(PyErr::fetch(py))
} else {
Expand All @@ -81,7 +82,7 @@ impl<T: PyTypeInfo + PyClassBaseType> PyObjectInit<T> for PyNativeTypeInitialize
subtype,
args,
kwargs,
T::NEW_ACCEPTS_ARGUMENTS,
T::OVERRIDE_TP_NEW,
)
}

Expand Down
6 changes: 3 additions & 3 deletions src/pyclass_init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@
//! Initialization of a pyo3 `#[pyclass] struct MyClass;`
//! - `MyClass(*args, **kwargs) == MyClass.__call__(*args, **kwargs) == type.__call__(*args, **kwargs)`
//! - Calls `obj = MyClass.__new__(MyClass, *args, **kwargs)`
//! - Calls user defined `#[new]` function, returning a [IntoPyCallbackOutput<PyClassInitializer>] which has
//! - Calls user defined `#[new]` function, returning a [`IntoPyCallbackOutput<PyClassInitializer>`] which has
//! instances of each user defined struct in the inheritance hierarchy.
//! - Calls [PyClassInitializer::create_class_object_of_type]
//! - Calls `PyClassInitializer::create_class_object_of_type`
//! - Recursively calls back to the base native type.
//! - At the base native type, [PyObjectInit::into_new_object] calls `__new__` for the base native type
//! (passing the [ffi::PyTypeObject] of the most derived type)
Expand All @@ -43,7 +43,7 @@
//!
//! ## Notes:
//! - pyo3 classes annotated with `#[pyclass(dict)]` have a `__dict__` attribute. When using the `tp_dictoffset`
//! mechanism instead of `Py_TPFLAGS_MANAGED_DICT` to enable this, the dict is stored in the [PyClassObjectContents]
//! mechanism instead of `Py_TPFLAGS_MANAGED_DICT` to enable this, the dict is stored in the `PyClassObjectContents`
//! of the most derived type and is set to NULL at construction and initialized to a new dictionary by
//! [ffi::PyObject_GenericGetDict] when first accessed.
//! - The python documentation also mentions 'static' classes which define their [ffi::PyTypeObject] in static/global
Expand Down
12 changes: 10 additions & 2 deletions src/types/any.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,16 @@ impl crate::impl_::pyclass::PyClassBaseType for PyAny {
type BaseNativeType = PyAny;
type Initializer = crate::impl_::pyclass_init::PyNativeTypeInitializer<Self>;
type PyClassMutability = crate::pycell::impl_::ImmutableClass;
// `object.__new__` should be called with only the type of object to create, without any other arguments.
const NEW_ACCEPTS_ARGUMENTS: bool = false;
/// `object.__new__` ([ffi::PyBaseObject_Type::tp_new]) has some unique behaviour that must be worked around:
/// - it does not accept any arguments (if args or kwargs are non-empty a `TypeError` is raised) and in the current
/// implementation, arguments are propagated to the native base class `__new__` function.
/// - it initializes `__dict__` if applicable (`#[pyclass(dict)]`). Currently this dict will leak because
/// the pointer to it will be overwritten when the rust-managed data is written to the pyobject.
/// - it checks for any abstract methods and refuses to construct if any are found. This is generally not applicable
/// to pyo3 classes.
///
/// on the other hand, [ffi::PyType_GenericNew] simply allocates space for the object without initializing anything.
const OVERRIDE_TP_NEW: Option<ffi::newfunc> = Some(ffi::PyType_GenericNew);
}

/// This trait represents the Python APIs which are usable on all Python objects.
Expand Down

0 comments on commit c5e2f75

Please sign in to comment.