Skip to content

Commit

Permalink
Add support for the number protocol
Browse files Browse the repository at this point in the history
NOTE: We now get warnings because of the modularization of the macro system
specified in RFC rust-lang/rfcs#1561

However, fixing these warnings is out of scope for this PR.
  • Loading branch information
Techcable committed Aug 9, 2021
1 parent e211a68 commit 78e4e4d
Show file tree
Hide file tree
Showing 5 changed files with 361 additions and 3 deletions.
7 changes: 6 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@
clippy::manual_strip,
clippy::match_like_matches_macro
)]
#![warn(
// TODO: We need to fix this
macro_expanded_macro_exports_accessed_by_absolute_paths,
)]

//! Rust bindings to the Python interpreter.
//!
Expand Down Expand Up @@ -210,13 +214,14 @@ pub mod buffer;
mod conversion;
mod err;
mod function;
mod objectprotocol;
mod objects;
mod python;
mod pythonrun;
//pub mod rustobject;
#[macro_use]
pub mod py_class;
mod sharedref;
mod objectprotocol;

#[cfg(feature = "serde-convert")]
pub mod serde;
Expand Down
4 changes: 4 additions & 0 deletions src/objectprotocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ use crate::ffi;
use crate::objects::{PyDict, PyObject, PyString, PyTuple};
use crate::python::{Python, PythonObject, ToPythonPointer};

mod number;

pub use self::number::NumberProtocol;

/// Trait that contains methods
pub trait ObjectProtocol: PythonObject {
/// Determines whether this object has the given attribute.
Expand Down
323 changes: 323 additions & 0 deletions src/objectprotocol/number.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,323 @@
use std::cmp::Ordering;
use std::fmt;

use crate::conversion::ToPyObject;
use crate::err::{self, PyErr, PyResult};
use crate::ffi;
use crate::objects::{PyObject, PyInt, PyLong, PyFloat};
use crate::python::{Python, PythonObject, ToPythonPointer};


use super::ObjectProtocol;

/// Operations on numeric objects
pub trait NumberProtocol: ObjectProtocol {
/// Perform addition (self + other)
///
/// Invokes the `__add__` magic-method
#[inline]
fn add(&self, py: Python, other: impl ToPyObject) -> PyResult<PyObject> {
other.with_borrowed_ptr(py, |other| unsafe {
err::result_from_owned_ptr(py, ffi::PyNumber_Add(self.as_ptr(), other))
})
}
/// Perform subtraction (self - other)
///
/// Invokes the `__sub__` magic-method
#[inline]
fn subtract(&self, py: Python, other: impl ToPyObject) -> PyResult<PyObject> {
other.with_borrowed_ptr(py, |other| unsafe {
err::result_from_owned_ptr(py, ffi::PyNumber_Subtract(self.as_ptr(), other))
})
}
/// Perform multiplication (self * other)
///
/// Invokes the `__mul__` magic-method
#[inline]
fn multiply(&self, py: Python, other: impl ToPyObject) -> PyResult<PyObject> {
other.with_borrowed_ptr(py, |other| unsafe {
err::result_from_owned_ptr(py, ffi::PyNumber_Multiply(self.as_ptr(), other))
})
}
/// Perform matrix multiplication, equivalent to the Python expression `self @ other`
///
/// Invokes the `__matmul__` magic-method
///
/// This was added in Python 3.5, and will unconditionally
/// throw an exception on any version before that.
///
/// See [PEP 0456](https://www.python.org/dev/peps/pep-0465/) for details.
#[inline]
fn matrix_multiply(&self, py: Python, other: impl ToPyObject) -> PyResult<PyObject> {
#[cfg(not(any(feature = "python3-4", feature = "python2-sys")))] {
other.with_borrowed_ptr(py, |other| unsafe {
err::result_from_owned_ptr(py, ffi::PyNumber_MatrixMultiply(self.as_ptr(), other))
})
}
#[cfg(any(feature = "python3-4", feature = "python2-sys"))] {

drop(other);
Err(crate::PyErr::new::<crate::exc::TypeError, _>(
py,
"Matrix multiplication is unsupported before Python 3.5"
))
}
}
/// Perform exponentiation, equivalent to the Python expression `self ** other`,
/// or the two-argument form of the builtin method pow: `pow(self, other)`
///
/// Invokes the `__pow__` magic-method
///
/// See also [NumberProtocol::pow_mod].
#[inline]
fn pow(&self, py: Python, other: impl ToPyObject) -> PyResult<PyObject> {
self.pow_mod(py, other, py.None())
}
/// Perform exponentiation modulo an integer,
/// mathematically equivalent to `self ** other % mod`
/// but computed much more efficiently.
///
/// Equivalent to invoking the three-argument form
/// of the builtin `pow` method: `pow(self, other, z)`
///
/// Invoking with a `None` for modulo is equivalent to
/// the regular power operation.
///
/// Invokes the `__pow__` magic-method
#[inline]
fn pow_mod(&self, py: Python, exp: impl ToPyObject, z: impl ToPyObject) -> PyResult<PyObject> {
exp.with_borrowed_ptr(py, |exp| {
z.with_borrowed_ptr(py, |z| unsafe {
err::result_from_owned_ptr(py, ffi::PyNumber_Power(self.as_ptr(), exp, z))
})
})
}
/// Perform the "true division" operation,
/// equivalent to the Python expression `self / other`,
///
/// Invokes the `__truediv__` magic-method.
#[inline]
fn true_div(&self, py: Python, other: impl ToPyObject) -> PyResult<PyObject> {
other.with_borrowed_ptr(py, |other| unsafe {
err::result_from_owned_ptr(py, ffi::PyNumber_TrueDivide(self.as_ptr(), other))
})
}
/// Perform the "floor division" operation,
/// equivalent to the Python expression `self // other`,
///
/// This method was added in Python 3.
/// If compiling against Python 2, it unconditional throws an error.
///
/// Invokes the `__floordiv__` magic-method.
#[inline]
fn div_floor(&self, py: Python, other: impl ToPyObject) -> PyResult<PyObject> {
other.with_borrowed_ptr(py, |other| unsafe {
err::result_from_owned_ptr(py, ffi::PyNumber_TrueDivide(self.as_ptr(), other))
})
}
/// Return the remainder of dividing `self` by `other`,
/// equivalent to the Python expression `self % other`
///
/// Invokes the `__mod__` magic-method.
#[inline]
fn remainder(&self, py: Python, other: impl ToPyObject) -> PyResult<PyObject> {
other.with_borrowed_ptr(py, |other| unsafe {
err::result_from_owned_ptr(py, ffi::PyNumber_Remainder(self.as_ptr(), other))
})
}
/// Perform combined division and modulo,
/// equivalent to the builtin method `divmod(self, other)`
///
/// Invokes the `__divmod__` magic-method.
#[inline]
fn div_mod(&self, py: Python, other: impl ToPyObject) -> PyResult<PyObject> {
other.with_borrowed_ptr(py, |other| unsafe {
err::result_from_owned_ptr(py, ffi::PyNumber_Divmod(self.as_ptr(), other))
})
}
/// Perform the negation of self (-self)
///
/// Invokes the `__neg__` magic-method.
#[inline]
fn negative(&self, py: Python) -> PyResult<PyObject> {
unsafe {
err::result_from_owned_ptr(py, ffi::PyNumber_Negative(self.as_ptr()))
}
}
/// Invoke the 'positive' operation, equivalent to the
/// Python expression `+self`
///
/// Invokes the `__pos__` magic-method
#[inline]
fn positive(&self, py: Python) -> PyResult<PyObject> {
unsafe {
err::result_from_owned_ptr(py, ffi::PyNumber_Positive(self.as_ptr()))
}
}
/// Return the absolute value of self,
/// equivalent to calling the builtin function `abs`
///
/// Invokes the `__abs__` magic-method.
#[inline]
fn absolute(&self, py: Python) -> PyResult<PyObject> {
unsafe {
err::result_from_owned_ptr(py, ffi::PyNumber_Absolute(self.as_ptr()))
}
}
/// Perform the bitwise negation of self,
/// equivalent to the Python expression `~self`
///
/// Invokes the `__invert__` magic-method
#[inline]
fn bitwise_invert(&self, py: Python) -> PyResult<PyObject> {
unsafe {
err::result_from_owned_ptr(py, ffi::PyNumber_Invert(self.as_ptr()))
}
}
/// Shift this value to the left by the specified number of bits,
/// equivalent to the Python expression `self << bits`
///
/// Invokes the `__lshift__` magic-method
#[inline]
fn left_shift(&self, py: Python, bits: impl ToPyObject) -> PyResult<PyObject> {
bits.with_borrowed_ptr(py, |other| unsafe {
err::result_from_owned_ptr(py, ffi::PyNumber_Lshift(self.as_ptr(), other))
})
}
/// Shift this value to the right by the specified number of bits,
/// equivalent to the Python expression `self >> bits`
///
/// Invokes the `__rshift__` magic-method
#[inline]
fn right_shift(&self, py: Python, bits: impl ToPyObject) -> PyResult<PyObject> {
bits.with_borrowed_ptr(py, |other| unsafe {
err::result_from_owned_ptr(py, ffi::PyNumber_Rshift(self.as_ptr(), other))
})
}
/// Perform the "bitwise and" of `self & other`
///
/// Invokes the `__and__` magic-method.
#[inline]
fn bitwise_and(&self, py: Python, other: impl ToPyObject) -> PyResult<PyObject> {
other.with_borrowed_ptr(py, |other| unsafe {
err::result_from_owned_ptr(py, ffi::PyNumber_And(self.as_ptr(), other))
})
}
/// Perform the "bitwise exclusive or",
/// equivalent to Python expression `self ^ other`
///
/// Invokes the `__xor__` magic-method.
#[inline]
fn bitwise_xor(&self, py: Python, other: impl ToPyObject) -> PyResult<PyObject> {
other.with_borrowed_ptr(py, |other| unsafe {
err::result_from_owned_ptr(py, ffi::PyNumber_Xor(self.as_ptr(), other))
})
}
/// Perform the "bitwise or" of `self | other`
///
/// Invokes the `__or__` magic-method.
#[inline]
fn bitwise_or(&self, py: Python, other: impl ToPyObject) -> PyResult<PyObject> {
other.with_borrowed_ptr(py, |other| unsafe {
err::result_from_owned_ptr(py, ffi::PyNumber_Or(self.as_ptr(), other))
})
}
/// Convert this object to an integer,
/// equivalent to the builtin function `int(self)`
///
/// Invokes the `__int__` magic-method.
///
/// Throws an exception if unable to perform
/// the conversion.
#[inline]
fn as_int(&self, py: Python) -> PyResult<PyLong> {
let obj = unsafe {
err::result_from_owned_ptr(py, ffi::PyNumber_Long(self.as_ptr()))?
};
Ok(obj.cast_into::<PyLong>(py)?)
}
/// Convert this object to a float,
/// equivalent to the builtin function `float(self)`
///
/// Invokes the `__float__` magic-method.
///
/// Throws an exception if unable to perform
/// the conversion.
#[inline]
fn as_float(&self, py: Python) -> PyResult<PyFloat> {
let obj = unsafe {
err::result_from_owned_ptr(py, ffi::PyNumber_Float(self.as_ptr()))?
};
Ok(obj.cast_into::<PyFloat>(py)?)
}
/// Losslessly convert this object to an integer index,
/// as if calling `operator.index()`
///
/// The presence of this method indicates
/// this object is an integer type.
///
/// Calls the `__index__` magic-method.
///
/// See also: [Documentation on the corresponding magic-method](https://docs.python.org/3/reference/datamodel.html?highlight=__index__#object.__index__)
#[inline]
fn as_int_index(&self, py: Python) -> PyResult<PyLong> {
let obj = unsafe {
err::result_from_owned_ptr(py, ffi::PyNumber_Long(self.as_ptr()))?
};
Ok(obj.cast_into::<PyLong>(py)?)
}
}

impl NumberProtocol for PyObject {}


#[cfg(test)]
mod test {
use crate::*;
use super::*;

#[test]
fn addition() {
let guard = Python::acquire_gil();
let py = guard.python();
let i1 = (5i32).to_py_object(py).into_object();
let i2 = (12i32).to_py_object(py).into_object();
let actual_res = i1.add(py, i2).unwrap();
let expected_res = (17i32).to_py_object(py).into_object();
assert_eq!(
actual_res.compare(py, expected_res).unwrap(),
Ordering::Equal
);
}

py_class!(class DummyMatMul |py| {
data number: i32;
def __new__(_cls, arg: i32) -> PyResult<DummyMatMul> {
DummyMatMul::create_instance(py, arg)
}
def __matmul__(left, other) -> PyResult<PyObject> {
// Do a dummy operation that can be easily tested
left.cast_as::<Self>(py)?.number(py)
.to_py_object(py)
.into_object()
.multiply(py, other)?
.add(py, 3)
}
});

#[test]
#[cfg_attr(any(feature = "python3-4", feature = "python2-sys"), should_panic)]
fn matrix_multiply() {
let guard = Python::acquire_gil();
let py = guard.python();
let first = DummyMatMul::create_instance(py, 5).unwrap().into_object();
let seven = (7i32).to_py_object(py).into_object();
let actual_res = first.matrix_multiply(py, seven).unwrap();
// 5 * 7 + 3 => 38
let expected_res = (38i32).to_py_object(py).into_object();
assert_eq!(
actual_res.compare(py, expected_res).unwrap(),
Ordering::Equal
);
}
}
2 changes: 1 addition & 1 deletion src/py_class/py_class_impl.py
Original file line number Diff line number Diff line change
Expand Up @@ -838,7 +838,7 @@ def inplace_numeric_operator(special_name, slot):
'__add__': binary_numeric_operator('nb_add'),
'__sub__': binary_numeric_operator('nb_subtract'),
'__mul__': binary_numeric_operator('nb_multiply'),
'__matmul__': unimplemented(),
'__matmul__': binary_numeric_operator('nb_matrix_multiply') if not PY2 else unimplemented(),
'__div__': unimplemented(),
'__truediv__': unimplemented(),
'__floordiv__': unimplemented(),
Expand Down
Loading

0 comments on commit 78e4e4d

Please sign in to comment.