Add documentation and tests for string conversion special methods.

This commit is contained in:
Daniel Grunwald 2016-05-06 21:24:32 +02:00
parent 3d99a4ac3c
commit 933e0ed11d
4 changed files with 65 additions and 69 deletions

View File

@ -132,7 +132,7 @@ Declares an instance method callable from Python.
* The return type must be `PyResult<T>` for some `T` that implements `ToPyObject`.
## Class methods
`@classmethod def method_name(cls, parameter-list) -> PyResult<...> { ... }
`@classmethod def method_name(cls, parameter-list) -> PyResult<...> { ... }`
Declares a class method callable from Python.
@ -143,7 +143,7 @@ Declares a class method callable from Python.
* The return type must be `PyResult<T>` for some `T` that implements `ToPyObject`.
## Static methods
`@staticmethod def method_name(parameter-list) -> PyResult<...> { ... }
`@staticmethod def method_name(parameter-list) -> PyResult<...> { ... }`
Declares a static method callable from Python.
@ -225,11 +225,10 @@ that use `RefCell::borrow_mut`.
Iterators can be defined using the Python special methods `__iter__` and `__next__`:
* `def __iter__(&self) -> PyResult<T>`
* `def __next__(&self) -> PyResult<Option<T>>`
* `def __iter__(&self) -> PyResult<impl ToPyObject>`
* `def __next__(&self) -> PyResult<Option<impl ToPyObject>>`
In both cases, `T` must be a type that implements `ToPyObject`.
Returning `Ok(None)` from `__next__` indicates that that there are no further items.
Returning `Ok(None)` from `__next__` indicates that that there are no further items.
Example:
@ -252,6 +251,31 @@ py_class!(class MyIterator |py| {
# fn main() {}
```
## String Conversions
* `def __repr__(&self) -> PyResult<impl ToPyObject<ObjectType=PyString>>`
* `def __str__(&self) -> PyResult<impl ToPyObject<ObjectType=PyString>>`
Possible return types for `__str__` and `__repr__` are `PyResult<String>` or `PyResult<PyString>`.
In Python 2.7, Unicode strings returned by `__str__` and `__repr__` will be converted to byte strings
by the Python runtime, which results in an exception if the string contains non-ASCII characters.
* `def __bytes__(&self) -> PyResult<PyBytes>`
On Python 3.x, provides the conversion to `bytes`.
On Python 2.7, `__bytes__` is allowed but has no effect.
* `def __unicode__(&self) -> PyResult<PyUnicode>`
On Python 2.7, provides the conversion to `unicode`.
On Python 3.x, `__unicode__` is allowed but has no effect.
* `def __format__(&self, format_spec: &str) -> PyResult<impl ToPyObject<ObjectType=PyString>>`
Special method that is used by the `format()` builtin and the `str.format()` method.
Possible return types are `PyResult<String>` or `PyResult<PyString>`.
*/
#[macro_export]
macro_rules! py_class {

View File

@ -584,8 +584,8 @@ special_names = {
'__del__': error('__del__ is not supported by py_class!; Use a data member with a Drop impl instead.'),
'__repr__': unary_operator('tp_repr', res_type="PyString"),
'__str__': unary_operator('tp_str', res_type="PyString"),
'__unicode__': unary_operator('tp_unicode', res_type="PyUnicode"),
'__bytes__': unary_operator('tp_bytes', res_type="PyBytes"),
'__unicode__': normal_method(),
'__bytes__': normal_method(),
'__format__': normal_method(),
# Comparison Operators
'__lt__': unimplemented(),

View File

@ -340,35 +340,6 @@ macro_rules! py_class_impl {
} => {
py_error! { "__bool__ is not supported by py_class! yet." }
};
{ $class:ident $py:ident $info:tt
/* slots: */ {
/* type_slots */ [ $( $tp_slot_name:ident : $tp_slot_value:expr, )* ]
$as_number:tt $as_sequence:tt
}
{ $( $imp:item )* }
$members:tt;
def __bytes__(&$slf:ident) -> $res_type:ty { $($body:tt)* } $($tail:tt)*
} => { py_class_impl! {
$class $py $info
/* slots: */ {
/* type_slots */ [
$( $tp_slot_name : $tp_slot_value, )*
tp_bytes: py_class_unary_slot!($class::__bytes__, *mut $crate::_detail::ffi::PyObject, $crate::_detail::PythonObjectCallbackConverter::<$crate::PyBytes>(::std::marker::PhantomData)),
]
$as_number $as_sequence
}
/* impl: */ {
$($imp)*
py_class_impl_item! { $class, $py, __bytes__(&$slf,) $res_type; { $($body)* } [] }
}
$members; $($tail)*
}};
// def __bytes__()
{ $class:ident $py:ident $info:tt $slots:tt $impls:tt $members:tt;
def __bytes__ $($tail:tt)*
} => {
py_error! { "Invalid signature for unary operator __bytes__" }
};
// def __call__()
{ $class:ident $py:ident $info:tt $slots:tt $impls:tt $members:tt;
def __call__ $($tail:tt)*
@ -1061,35 +1032,6 @@ macro_rules! py_class_impl {
} => {
py_error! { "__truediv__ is not supported by py_class! yet." }
};
{ $class:ident $py:ident $info:tt
/* slots: */ {
/* type_slots */ [ $( $tp_slot_name:ident : $tp_slot_value:expr, )* ]
$as_number:tt $as_sequence:tt
}
{ $( $imp:item )* }
$members:tt;
def __unicode__(&$slf:ident) -> $res_type:ty { $($body:tt)* } $($tail:tt)*
} => { py_class_impl! {
$class $py $info
/* slots: */ {
/* type_slots */ [
$( $tp_slot_name : $tp_slot_value, )*
tp_unicode: py_class_unary_slot!($class::__unicode__, *mut $crate::_detail::ffi::PyObject, $crate::_detail::PythonObjectCallbackConverter::<$crate::PyUnicode>(::std::marker::PhantomData)),
]
$as_number $as_sequence
}
/* impl: */ {
$($imp)*
py_class_impl_item! { $class, $py, __unicode__(&$slf,) $res_type; { $($body)* } [] }
}
$members; $($tail)*
}};
// def __unicode__()
{ $class:ident $py:ident $info:tt $slots:tt $impls:tt $members:tt;
def __unicode__ $($tail:tt)*
} => {
py_error! { "Invalid signature for unary operator __unicode__" }
};
// def __xor__()
{ $class:ident $py:ident $info:tt $slots:tt $impls:tt $members:tt;
def __xor__ $($tail:tt)*

View File

@ -2,7 +2,8 @@
#[macro_use] extern crate cpython;
use cpython::{PyObject, PythonObject, PyDrop, PyClone, PyResult, Python, NoArgs, ObjectProtocol, PyDict, exc};
use cpython::{PyObject, PythonObject, PyDrop, PyClone, PyResult, Python, NoArgs, ObjectProtocol,
PyDict, PyBytes, PyUnicode, exc};
use std::{mem, isize, iter};
use std::cell::RefCell;
use std::sync::Arc;
@ -339,8 +340,16 @@ py_class!(class StringMethods |py| {
Ok("repr")
}
def __format__(&self, formatspec: &str) -> PyResult<String> {
Ok(format!("format({})", formatspec))
def __format__(&self, format_spec: &str) -> PyResult<String> {
Ok(format!("format({})", format_spec))
}
def __unicode__(&self) -> PyResult<PyUnicode> {
Ok(PyUnicode::new(py, "unicode"))
}
def __bytes__(&self) -> PyResult<PyBytes> {
Ok(PyBytes::new(py, b"bytes"))
}
});
@ -355,3 +364,24 @@ fn string_methods() {
py_assert!(py, obj, "'{0:x}'.format(obj) == 'format(x)'");
}
#[test]
#[cfg(feature="python27-sys")]
fn python2_string_methods() {
let gil = Python::acquire_gil();
let py = gil.python();
let obj = StringMethods::create_instance(py).unwrap();
py_assert!(py, obj, "unicode(obj) == u'unicode'");
}
#[test]
#[cfg(feature="python3-sys")]
fn python3_string_methods() {
let gil = Python::acquire_gil();
let py = gil.python();
let obj = StringMethods::create_instance(py).unwrap();
py_assert!(py, obj, "bytes(obj) == b'bytes'");
}