From 33871b7aea498175e67737e8ab14f9e2d6ff3071 Mon Sep 17 00:00:00 2001 From: David Hewitt <1939362+davidhewitt@users.noreply.github.com> Date: Thu, 8 Dec 2022 08:29:09 +0000 Subject: [PATCH 1/3] allow **kwargs to take arguments which conflict with positional-only parameters --- newsfragments/2800.fixed.md | 1 + src/impl_/extract_argument.rs | 183 +++++++++++++++++----------------- tests/test_methods.rs | 26 +++++ 3 files changed, 121 insertions(+), 89 deletions(-) create mode 100644 newsfragments/2800.fixed.md diff --git a/newsfragments/2800.fixed.md b/newsfragments/2800.fixed.md new file mode 100644 index 00000000000..6a5b8503367 --- /dev/null +++ b/newsfragments/2800.fixed.md @@ -0,0 +1 @@ +Allow functions taking `**kwargs` to accept keyword arguments which share a name with a positional-only argument (as permitted by PEP 570). diff --git a/src/impl_/extract_argument.rs b/src/impl_/extract_argument.rs index 0c42cd63c3f..dd361e4b538 100644 --- a/src/impl_/extract_argument.rs +++ b/src/impl_/extract_argument.rs @@ -237,17 +237,9 @@ impl FunctionDescription { V: VarargsHandler<'py>, K: VarkeywordsHandler<'py>, { - // Safety: Option<&PyAny> has the same memory layout as `*mut ffi::PyObject` - let args = args as *const Option<&PyAny>; - let positional_args_provided = nargs as usize; - let args_slice = if args.is_null() { - &[] - } else { - std::slice::from_raw_parts(args, positional_args_provided) - }; - let num_positional_parameters = self.positional_parameter_names.len(); + debug_assert!(nargs >= 0); debug_assert!(self.positional_only_parameters <= num_positional_parameters); debug_assert!(self.required_positional_parameters <= num_positional_parameters); debug_assert_eq!( @@ -255,57 +247,39 @@ impl FunctionDescription { num_positional_parameters + self.keyword_only_parameters.len() ); - let varargs = if positional_args_provided > num_positional_parameters { - let (positional_parameters, varargs) = args_slice.split_at(num_positional_parameters); - output[..num_positional_parameters].copy_from_slice(positional_parameters); - V::handle_varargs_fastcall(py, varargs, self)? + // Handle positional arguments + // Safety: Option<&PyAny> has the same memory layout as `*mut ffi::PyObject` + let args: *const Option<&PyAny> = args.cast(); + let positional_args_provided = nargs as usize; + let remaining_positional_args = if args.is_null() { + debug_assert_eq!(positional_args_provided, 0); + &[] } else { - output[..positional_args_provided].copy_from_slice(args_slice); - V::handle_varargs_fastcall(py, &[], self)? + // Can consume at most the number of positional parameters in the function definition, + // the rest are varargs. + let positional_args_to_consume = + num_positional_parameters.min(positional_args_provided); + let (positional_parameters, remaining) = + std::slice::from_raw_parts(args, positional_args_provided) + .split_at(positional_args_to_consume); + output[..positional_args_to_consume].copy_from_slice(positional_parameters); + remaining }; + let varargs = V::handle_varargs_fastcall(py, remaining_positional_args, self)?; // Handle keyword arguments - let mut varkeywords = Default::default(); + let mut varkeywords = K::Varkeywords::default(); if let Some(kwnames) = py.from_borrowed_ptr_or_opt::(kwnames) { - let mut positional_only_keyword_arguments = Vec::new(); - // Safety: &PyAny has the same memory layout as `*mut ffi::PyObject` let kwargs = ::std::slice::from_raw_parts((args as *const &PyAny).offset(nargs), kwnames.len()); - for (kwarg_name_py, &value) in kwnames.iter().zip(kwargs) { - // All keyword arguments should be UTF8 strings, but we'll check, just in case. - if let Ok(kwarg_name) = kwarg_name_py.downcast::()?.to_str() { - // Try to place parameter in keyword only parameters - if let Some(i) = self.find_keyword_parameter_in_keyword_only(kwarg_name) { - if output[i + num_positional_parameters] - .replace(value) - .is_some() - { - return Err(self.multiple_values_for_argument(kwarg_name)); - } - continue; - } - - // Repeat for positional parameters - if let Some(i) = self.find_keyword_parameter_in_positional(kwarg_name) { - if i < self.positional_only_parameters { - positional_only_keyword_arguments.push(kwarg_name); - } else if output[i].replace(value).is_some() { - return Err(self.multiple_values_for_argument(kwarg_name)); - } - continue; - } - }; - - K::handle_unexpected_keyword(&mut varkeywords, kwarg_name_py, value, self)? - } - - if !positional_only_keyword_arguments.is_empty() { - return Err( - self.positional_only_keyword_arguments(&positional_only_keyword_arguments) - ); - } + self.handle_kwargs::( + kwnames.iter().zip(kwargs.iter().copied()), + &mut varkeywords, + num_positional_parameters, + output, + )? } // Once all inputs have been processed, check that all required arguments have been provided. @@ -360,50 +334,81 @@ impl FunctionDescription { let varargs = V::handle_varargs_tuple(args, self)?; // Handle keyword arguments - let mut varkeywords = Default::default(); + let mut varkeywords = K::Varkeywords::default(); if let Some(kwargs) = kwargs { - let mut positional_only_keyword_arguments = Vec::new(); - for (kwarg_name_py, value) in kwargs { - // All keyword arguments should be UTF8 strings, but we'll check, just in case. - if let Ok(kwarg_name) = kwarg_name_py.downcast::()?.to_str() { - // Try to place parameter in keyword only parameters - if let Some(i) = self.find_keyword_parameter_in_keyword_only(kwarg_name) { - if output[i + num_positional_parameters] - .replace(value) - .is_some() - { - return Err(self.multiple_values_for_argument(kwarg_name)); - } - continue; + self.handle_kwargs::(kwargs, &mut varkeywords, num_positional_parameters, output)? + } + + // Once all inputs have been processed, check that all required arguments have been provided. + + self.ensure_no_missing_required_positional_arguments(output, args.len())?; + self.ensure_no_missing_required_keyword_arguments(output)?; + + Ok((varargs, varkeywords)) + } + + #[inline] + fn handle_kwargs<'py, K, I>( + &self, + kwargs: I, + varkeywords: &mut K::Varkeywords, + num_positional_parameters: usize, + output: &mut [Option<&'py PyAny>], + ) -> PyResult<()> + where + K: VarkeywordsHandler<'py>, + I: IntoIterator, + { + debug_assert_eq!( + num_positional_parameters, + self.positional_parameter_names.len() + ); + debug_assert_eq!( + output.len(), + num_positional_parameters + self.keyword_only_parameters.len() + ); + let mut positional_only_keyword_arguments = Vec::new(); + for (kwarg_name_py, value) in kwargs { + // All keyword arguments should be UTF-8 strings, but we'll check, just in case. + // If it isn't, then it will be handled below as a varkeyword (which may raise an + // error if this function doesn't accept **kwargs). Rust source is always UTF-8 + // and so all argument names in `#[pyfunction]` signature must be UTF-8. + if let Ok(kwarg_name) = kwarg_name_py.downcast::()?.to_str() { + // Try to place parameter in keyword only parameters + if let Some(i) = self.find_keyword_parameter_in_keyword_only(kwarg_name) { + if output[i + num_positional_parameters] + .replace(value) + .is_some() + { + return Err(self.multiple_values_for_argument(kwarg_name)); } + continue; + } - // Repeat for positional parameters - if let Some(i) = self.find_keyword_parameter_in_positional(kwarg_name) { - if i < self.positional_only_parameters { + // Repeat for positional parameters + if let Some(i) = self.find_keyword_parameter_in_positional(kwarg_name) { + if i < self.positional_only_parameters { + // If accepting **kwargs, then it's allowed for the name of the + // kwarg to conflict with a postional-only argument - the value + // will go into **kwargs anyway. + if K::handle_varkeyword(varkeywords, kwarg_name_py, value, self).is_err() { positional_only_keyword_arguments.push(kwarg_name); - } else if output[i].replace(value).is_some() { - return Err(self.multiple_values_for_argument(kwarg_name)); } - continue; + } else if output[i].replace(value).is_some() { + return Err(self.multiple_values_for_argument(kwarg_name)); } - }; - - K::handle_unexpected_keyword(&mut varkeywords, kwarg_name_py, value, self)? - } + continue; + } + }; - if !positional_only_keyword_arguments.is_empty() { - return Err( - self.positional_only_keyword_arguments(&positional_only_keyword_arguments) - ); - } + K::handle_varkeyword(varkeywords, kwarg_name_py, value, self)? } - // Once all inputs have been processed, check that all required arguments have been provided. - - self.ensure_no_missing_required_positional_arguments(output, args.len())?; - self.ensure_no_missing_required_keyword_arguments(output)?; + if !positional_only_keyword_arguments.is_empty() { + return Err(self.positional_only_keyword_arguments(&positional_only_keyword_arguments)); + } - Ok((varargs, varkeywords)) + Ok(()) } #[inline] @@ -637,10 +642,10 @@ impl<'py> VarargsHandler<'py> for TupleVarargs { } } -/// A trait used to control whether to accept unrecognised keywords in FunctionDescription::extract_argument_(method) functions. +/// A trait used to control whether to accept varkeywords in FunctionDescription::extract_argument_(method) functions. pub trait VarkeywordsHandler<'py> { type Varkeywords: Default; - fn handle_unexpected_keyword( + fn handle_varkeyword( varkeywords: &mut Self::Varkeywords, name: &'py PyAny, value: &'py PyAny, @@ -654,7 +659,7 @@ pub struct NoVarkeywords; impl<'py> VarkeywordsHandler<'py> for NoVarkeywords { type Varkeywords = (); #[inline] - fn handle_unexpected_keyword( + fn handle_varkeyword( _varkeywords: &mut Self::Varkeywords, name: &'py PyAny, _value: &'py PyAny, @@ -670,7 +675,7 @@ pub struct DictVarkeywords; impl<'py> VarkeywordsHandler<'py> for DictVarkeywords { type Varkeywords = Option<&'py PyDict>; #[inline] - fn handle_unexpected_keyword( + fn handle_varkeyword( varkeywords: &mut Self::Varkeywords, name: &'py PyAny, value: &'py PyAny, diff --git a/tests/test_methods.rs b/tests/test_methods.rs index 9dddbd351be..706c5494ee2 100644 --- a/tests/test_methods.rs +++ b/tests/test_methods.rs @@ -711,6 +711,16 @@ impl MethSignature { [a.to_object(py), kwargs.to_object(py)].to_object(py) } + #[pyo3(signature = (a=0, /, **kwargs))] + fn get_optional_pos_only_with_kwargs( + &self, + py: Python<'_>, + a: i32, + kwargs: Option<&PyDict>, + ) -> PyObject { + [a.to_object(py), kwargs.to_object(py)].to_object(py) + } + #[pyo3(signature = (*, a = 2, b = 3))] fn get_kwargs_only_with_defaults(&self, a: i32, b: i32) -> i32 { a + b @@ -961,6 +971,22 @@ fn meth_signature() { PyTypeError ); + py_run!( + py, + inst, + "assert inst.get_optional_pos_only_with_kwargs() == [0, None]" + ); + py_run!( + py, + inst, + "assert inst.get_optional_pos_only_with_kwargs(10) == [10, None]" + ); + py_run!( + py, + inst, + "assert inst.get_optional_pos_only_with_kwargs(a=10) == [0, {'a': 10}]" + ); + py_run!(py, inst, "assert inst.get_kwargs_only_with_defaults() == 5"); py_run!( py, From 919b4c02a55e6697a7577225ac1df0c220bd36eb Mon Sep 17 00:00:00 2001 From: David Hewitt <1939362+davidhewitt@users.noreply.github.com> Date: Tue, 22 Nov 2022 22:19:12 +0000 Subject: [PATCH 2/3] ci: add Python 3.12-dev jobs --- .github/workflows/ci.yml | 1 + pyo3-ffi/src/cpython/unicodeobject.rs | 46 +++++++++++++++++- pyo3-ffi/src/unicodeobject.rs | 2 + src/ffi/tests.rs | 67 ++++++++++++++++++++------- 4 files changed, 98 insertions(+), 18 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fd190424f71..43020c5d62f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -141,6 +141,7 @@ jobs: "3.9", "3.10", "3.11", + "3.12-dev", "pypy-3.7", "pypy-3.8", "pypy-3.9" diff --git a/pyo3-ffi/src/cpython/unicodeobject.rs b/pyo3-ffi/src/cpython/unicodeobject.rs index 9ec21bf6989..7fa2389f4c4 100644 --- a/pyo3-ffi/src/cpython/unicodeobject.rs +++ b/pyo3-ffi/src/cpython/unicodeobject.rs @@ -1,6 +1,7 @@ #[cfg(not(PyPy))] use crate::Py_hash_t; use crate::{PyObject, Py_UCS1, Py_UCS2, Py_UCS4, Py_UNICODE, Py_ssize_t}; +#[cfg(not(Py_3_12))] use libc::wchar_t; use std::os::raw::{c_char, c_int, c_uint, c_void}; @@ -48,6 +49,7 @@ pub struct PyASCIIObject { /// unsigned int ready:1; /// unsigned int :24; pub state: u32, + #[cfg(not(Py_3_12))] pub wstr: *mut wchar_t, } @@ -56,6 +58,7 @@ pub struct PyASCIIObject { /// In addition, they are disabled on big-endian architectures to restrict this to most "common" /// platforms, which are at least tested on CI and appear to be sound. #[cfg(target_endian = "little")] +#[cfg(not(Py_3_12))] impl PyASCIIObject { #[inline] pub unsafe fn interned(&self) -> c_uint { @@ -83,11 +86,35 @@ impl PyASCIIObject { } } +#[cfg(Py_3_12)] +impl PyASCIIObject { + #[inline] + pub unsafe fn interned(&self) -> c_uint { + self.state & 1 + } + + #[inline] + pub unsafe fn kind(&self) -> c_uint { + (self.state >> 1) & 7 + } + + #[inline] + pub unsafe fn compact(&self) -> c_uint { + (self.state >> 4) & 1 + } + + #[inline] + pub unsafe fn ascii(&self) -> c_uint { + (self.state >> 5) & 1 + } +} + #[repr(C)] pub struct PyCompactUnicodeObject { pub _base: PyASCIIObject, pub utf8_length: Py_ssize_t, pub utf8: *mut c_char, + #[cfg(not(Py_3_12))] pub wstr_length: Py_ssize_t, } @@ -123,6 +150,7 @@ pub const SSTATE_INTERNED_IMMORTAL: c_uint = 2; #[cfg(target_endian = "little")] pub unsafe fn PyUnicode_IS_ASCII(op: *mut PyObject) -> c_uint { debug_assert!(crate::PyUnicode_Check(op) != 0); + #[cfg(not(Py_3_12))] debug_assert!(PyUnicode_IS_READY(op) != 0); (*(op as *mut PyASCIIObject)).ascii() @@ -141,7 +169,7 @@ pub unsafe fn PyUnicode_IS_COMPACT_ASCII(op: *mut PyObject) -> c_uint { } #[cfg(not(Py_3_12))] -#[cfg_attr(Py_3_10, deprecated(note = "Python 3.10"))] +#[deprecated(note = "Removed in Python 3.12")] pub const PyUnicode_WCHAR_KIND: c_uint = 0; pub const PyUnicode_1BYTE_KIND: c_uint = 1; @@ -170,6 +198,7 @@ pub unsafe fn PyUnicode_4BYTE_DATA(op: *mut PyObject) -> *mut Py_UCS4 { #[cfg(target_endian = "little")] pub unsafe fn PyUnicode_KIND(op: *mut PyObject) -> c_uint { debug_assert!(crate::PyUnicode_Check(op) != 0); + #[cfg(not(Py_3_12))] debug_assert!(PyUnicode_IS_READY(op) != 0); (*(op as *mut PyASCIIObject)).kind() @@ -213,19 +242,26 @@ pub unsafe fn PyUnicode_DATA(op: *mut PyObject) -> *mut c_void { #[cfg(target_endian = "little")] pub unsafe fn PyUnicode_GET_LENGTH(op: *mut PyObject) -> Py_ssize_t { debug_assert!(crate::PyUnicode_Check(op) != 0); + #[cfg(not(Py_3_12))] debug_assert!(PyUnicode_IS_READY(op) != 0); (*(op as *mut PyASCIIObject)).length } #[inline] +#[cfg(not(Py_3_12))] #[cfg(target_endian = "little")] pub unsafe fn PyUnicode_IS_READY(op: *mut PyObject) -> c_uint { (*(op as *mut PyASCIIObject)).ready() } +#[inline] +#[cfg(Py_3_12)] +pub unsafe fn PyUnicode_IS_READY(_: *mut PyObject) -> c_uint { + 1 +} + #[cfg(not(Py_3_12))] -#[cfg_attr(Py_3_10, deprecated(note = "Python 3.10"))] #[inline] #[cfg(target_endian = "little")] pub unsafe fn PyUnicode_READY(op: *mut PyObject) -> c_int { @@ -238,6 +274,12 @@ pub unsafe fn PyUnicode_READY(op: *mut PyObject) -> c_int { } } +#[cfg(Py_3_12)] +#[inline] +pub unsafe fn PyUnicode_READY(_: *mut PyObject) -> c_int { + 0 +} + // skipped PyUnicode_MAX_CHAR_VALUE // skipped _PyUnicode_get_wstr_length // skipped PyUnicode_WSTR_LENGTH diff --git a/pyo3-ffi/src/unicodeobject.rs b/pyo3-ffi/src/unicodeobject.rs index 5841fa9e194..509bf81fbdd 100644 --- a/pyo3-ffi/src/unicodeobject.rs +++ b/pyo3-ffi/src/unicodeobject.rs @@ -59,6 +59,8 @@ extern "C" { pub fn PyUnicode_AsUCS4Copy(unicode: *mut PyObject) -> *mut Py_UCS4; #[cfg_attr(PyPy, link_name = "PyPyUnicode_GetLength")] pub fn PyUnicode_GetLength(unicode: *mut PyObject) -> Py_ssize_t; + #[cfg(not(Py_3_12))] + #[deprecated(note = "Removed in Python 3.12")] #[cfg_attr(PyPy, link_name = "PyPyUnicode_GetSize")] pub fn PyUnicode_GetSize(unicode: *mut PyObject) -> Py_ssize_t; pub fn PyUnicode_ReadChar(unicode: *mut PyObject, index: Py_ssize_t) -> Py_UCS4; diff --git a/src/ffi/tests.rs b/src/ffi/tests.rs index a0d32504497..edc193a7307 100644 --- a/src/ffi/tests.rs +++ b/src/ffi/tests.rs @@ -3,7 +3,7 @@ use crate::{types::PyDict, AsPyPointer, IntoPy, Py, PyAny, Python}; #[cfg(target_endian = "little")] use crate::types::PyString; -#[cfg(target_endian = "little")] +#[cfg(all(target_endian = "little", not(Py_3_12)))] use libc::wchar_t; #[cfg_attr(target_arch = "wasm32", ignore)] // DateTime import fails on wasm for mysterious reasons @@ -119,6 +119,7 @@ fn ascii_object_bitfield() { #[cfg(not(PyPy))] hash: 0, state: 0, + #[cfg(not(Py_3_12))] wstr: std::ptr::null_mut() as *mut wchar_t, }; @@ -127,26 +128,58 @@ fn ascii_object_bitfield() { assert_eq!(o.kind(), 0); assert_eq!(o.compact(), 0); assert_eq!(o.ascii(), 0); + #[cfg(not(Py_3_12))] assert_eq!(o.ready(), 0); - for i in 0..4 { - o.state = i; - assert_eq!(o.interned(), i); + #[cfg(not(Py_3_12))] + { + for i in 0..4 { + o.state = i; + assert_eq!(o.interned(), i); + } + + for i in 0..8 { + o.state = i << 2; + assert_eq!(o.kind(), i); + } + + o.state = 0; + assert_eq!(o.compact(), 0); + o.state = 1 << 5; + assert_eq!(o.compact(), 1); + + o.state = 0; + assert_eq!(o.ascii(), 0); + o.state = 1 << 6; + assert_eq!(o.ascii(), 1); + + o.state = 0; + assert_eq!(o.ready(), 0); + o.state = 1 << 7; + assert_eq!(o.ready(), 1); } - for i in 0..8 { - o.state = i << 2; - assert_eq!(o.kind(), i); + #[cfg(Py_3_12)] + { + assert_eq!(o.interned(), 0); + o.state = 1; + assert_eq!(o.interned(), 1); + + for i in 0..8 { + o.state = i << 1; + assert_eq!(o.kind(), i); + } + + o.state = 0; + assert_eq!(o.compact(), 0); + o.state = 1 << 4; + assert_eq!(o.compact(), 1); + + o.state = 0; + assert_eq!(o.ascii(), 0); + o.state = 1 << 5; + assert_eq!(o.ascii(), 1); } - - o.state = 1 << 5; - assert_eq!(o.compact(), 1); - - o.state = 1 << 6; - assert_eq!(o.ascii(), 1); - - o.state = 1 << 7; - assert_eq!(o.ready(), 1); } } @@ -167,6 +200,7 @@ fn ascii() { assert_eq!(ascii.kind(), PyUnicode_1BYTE_KIND); assert_eq!(ascii.compact(), 1); assert_eq!(ascii.ascii(), 1); + #[cfg(not(Py_3_12))] assert_eq!(ascii.ready(), 1); assert_eq!(PyUnicode_IS_ASCII(ptr), 1); @@ -208,6 +242,7 @@ fn ucs4() { assert_eq!(ascii.kind(), PyUnicode_4BYTE_KIND); assert_eq!(ascii.compact(), 1); assert_eq!(ascii.ascii(), 0); + #[cfg(not(Py_3_12))] assert_eq!(ascii.ready(), 1); assert_eq!(PyUnicode_IS_ASCII(ptr), 0); From 97d643274a5abe5ec6a19484f9ba32883aca7fc0 Mon Sep 17 00:00:00 2001 From: David Hewitt <1939362+davidhewitt@users.noreply.github.com> Date: Sat, 17 Dec 2022 07:49:51 +0000 Subject: [PATCH 3/3] adjust vectorcall symbols for pypy --- newsfragments/2811.added.md | 1 + newsfragments/2811.fixed.md | 1 + pyo3-ffi/src/cpython/abstract_.rs | 5 +++-- 3 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 newsfragments/2811.added.md create mode 100644 newsfragments/2811.fixed.md diff --git a/newsfragments/2811.added.md b/newsfragments/2811.added.md new file mode 100644 index 00000000000..6324253291d --- /dev/null +++ b/newsfragments/2811.added.md @@ -0,0 +1 @@ +Add FFI definitions `PyVectorcall_NARGS` and `PY_VECTORCALL_ARGUMENTS_OFFSET` for PyPy 3.8 and up. diff --git a/newsfragments/2811.fixed.md b/newsfragments/2811.fixed.md new file mode 100644 index 00000000000..0011182c828 --- /dev/null +++ b/newsfragments/2811.fixed.md @@ -0,0 +1 @@ +Fix unresolved symbol for `PyObject_Vectorcall` on PyPy 3.9 and up. diff --git a/pyo3-ffi/src/cpython/abstract_.rs b/pyo3-ffi/src/cpython/abstract_.rs index 25b1a5fccbc..326aec9f479 100644 --- a/pyo3-ffi/src/cpython/abstract_.rs +++ b/pyo3-ffi/src/cpython/abstract_.rs @@ -39,11 +39,11 @@ extern "C" { ) -> *mut PyObject; } -#[cfg(all(Py_3_8, not(PyPy)))] +#[cfg(all(Py_3_8))] const PY_VECTORCALL_ARGUMENTS_OFFSET: Py_ssize_t = 1 << (8 * std::mem::size_of::() as Py_ssize_t - 1); -#[cfg(all(Py_3_8, not(PyPy)))] +#[cfg(all(Py_3_8))] #[inline(always)] pub unsafe fn PyVectorcall_NARGS(n: size_t) -> Py_ssize_t { assert!(n <= (PY_SSIZE_T_MAX as size_t)); @@ -103,6 +103,7 @@ pub unsafe fn PyObject_Vectorcall( extern "C" { #[cfg(all(PyPy, Py_3_8))] #[cfg_attr(not(Py_3_9), link_name = "_PyPyObject_Vectorcall")] + #[cfg_attr(Py_3_9, link_name = "PyPyObject_Vectorcall")] pub fn PyObject_Vectorcall( callable: *mut PyObject, args: *const *mut PyObject,