From 66f2cd81a859319612e0f3864f54de8c400a6566 Mon Sep 17 00:00:00 2001 From: Dave Abrahams Date: Tue, 18 Jun 2002 13:49:09 +0000 Subject: [PATCH] object operator support [SVN r14168] --- Jamfile | 1 + .../boost/python/converter/object_manager.hpp | 6 +- include/boost/python/object.hpp | 1 + include/boost/python/object_attributes.hpp | 55 +- include/boost/python/object_core.hpp | 189 ++++--- include/boost/python/object_items.hpp | 51 +- include/boost/python/object_operators.hpp | 68 +++ include/boost/python/object_protocol_core.hpp | 18 +- include/boost/python/operators2.hpp | 9 +- include/boost/python/proxy.hpp | 75 +-- src/object_operators.cpp | 51 ++ src/object_protocol.cpp | 12 +- test/doctest.py | 495 ++++++++++-------- test/object.cpp | 164 +++++- test/object.py | 8 + 15 files changed, 771 insertions(+), 432 deletions(-) create mode 100644 include/boost/python/object_operators.hpp create mode 100644 src/object_operators.cpp diff --git a/Jamfile b/Jamfile index b0993452..72e4b3ec 100644 --- a/Jamfile +++ b/Jamfile @@ -28,6 +28,7 @@ dll bpl src/converter/callback.cpp src/object/iterator.cpp src/object_protocol.cpp + src/object_operators.cpp : $(BOOST_PYTHON_V2_PROPERTIES) BOOST_PYTHON_SOURCE diff --git a/include/boost/python/converter/object_manager.hpp b/include/boost/python/converter/object_manager.hpp index 93c394e2..6ffe6b1e 100755 --- a/include/boost/python/converter/object_manager.hpp +++ b/include/boost/python/converter/object_manager.hpp @@ -12,11 +12,11 @@ # include # include -namespace boost { namespace python { +namespace boost { namespace python { namespace api { class object; -}} +}}} namespace boost { namespace python { namespace converter { @@ -142,7 +142,7 @@ inline T* get_managed_object(python::detail::borrowed const volatile* p) // forward declaration needed because name lookup is bound by the // definition context. -PyObject* get_managed_object(python::object const&); +PyObject* get_managed_object(python::api::object const&); }}} // namespace boost::python::converter diff --git a/include/boost/python/object.hpp b/include/boost/python/object.hpp index 6720298c..5186669e 100755 --- a/include/boost/python/object.hpp +++ b/include/boost/python/object.hpp @@ -9,6 +9,7 @@ # include # include # include +# include namespace boost { namespace python { diff --git a/include/boost/python/object_attributes.hpp b/include/boost/python/object_attributes.hpp index a7bd6a8e..f75f76d9 100755 --- a/include/boost/python/object_attributes.hpp +++ b/include/boost/python/object_attributes.hpp @@ -10,51 +10,48 @@ # include # include -namespace boost { namespace python { +namespace boost { namespace python { namespace api { -namespace detail +struct const_attribute_policies { - struct const_attribute_policies - { - static object get(object const& target, object const& key); - }; + static object get(object const& target, object const& key); +}; - struct attribute_policies : const_attribute_policies - { - static object const& set(object const& target, object const& key, object const& value); - }; -} +struct attribute_policies : const_attribute_policies +{ + static object const& set(object const& target, object const& key, object const& value); +}; // // implementation // -inline object_attribute object::_(char const* name) +inline object_attribute object::attr(char const* name) { return object_attribute(*this, object(name)); } -inline const_object_attribute object::_(char const* name) const +inline const_object_attribute object::attr(char const* name) const { return const_object_attribute(*this, object(name)); } -namespace detail -{ - inline object const_attribute_policies::get(object const& target, object const& key) - { - return python::getattr(target, key); - } - inline object const& attribute_policies::set( - object const& target - , object const& key - , object const& value) - { - python::setattr(target, key, value); - return value; - } +inline object const_attribute_policies::get(object const& target, object const& key) +{ + return python::getattr(target, key); } +inline object const& attribute_policies::set( + object const& target + , object const& key + , object const& value) +{ + python::setattr(target, key, value); + return value; +} + +} // namespace api + namespace converter { // These specializations are a lie; the proxies do not directly @@ -64,13 +61,13 @@ namespace converter // actually managing the object during the duration of the // conversion. template <> - struct is_object_manager + struct is_object_manager { BOOST_STATIC_CONSTANT(bool, value = true); }; template <> - struct is_object_manager + struct is_object_manager { BOOST_STATIC_CONSTANT(bool, value = true); }; diff --git a/include/boost/python/object_core.hpp b/include/boost/python/object_core.hpp index 80ebf2c0..e243b1ee 100755 --- a/include/boost/python/object_core.hpp +++ b/include/boost/python/object_core.hpp @@ -12,122 +12,147 @@ namespace boost { namespace python { +// Put this in an inner namespace so that the generalized operators won't take over +namespace api +{ + // This file contains the definition of the object class and enough to // construct/copy it, but not enough to do operations like // attribute/item access or addition. -template class proxy; -namespace detail -{ + template class proxy; + struct const_attribute_policies; struct attribute_policies; struct const_item_policies; struct item_policies; -} -typedef proxy const_object_attribute; -typedef proxy object_attribute; -typedef proxy const_object_item; -typedef proxy object_item; -class object -{ + typedef proxy const_object_attribute; + typedef proxy object_attribute; + typedef proxy const_object_item; + typedef proxy object_item; + + // A way to turn a conrete type T into a type dependent on U. This + // keeps conforming compilers from complaining about returning an + // incomplete T from a template member function (which must be + // defined in the class body to keep MSVC happy). + template + struct dependent + { + typedef T type; + }; + + class object + { # if !defined(BOOST_MSVC) || BOOST_MSVC > 1200 - typedef object const& self_cref; + typedef object const& self_cref; # else - typedef object self_cref; + typedef object self_cref; # endif - public: + public: # ifndef BOOST_NO_FUNCTION_TEMPLATE_ORDERING - // copy constructor without NULL checking, for efficiency - object(object const&); + // copy constructor without NULL checking, for efficiency + object(object const&); # endif - // explicit conversion from any C++ object to Python - template - explicit object(T const& x) - : m_ptr( - python::borrowed( - python::allow_null( // null check is already done - converter::arg_to_python(x).get()) - ) - ) - { - } + // explicit conversion from any C++ object to Python + template + explicit object(T const& x) + : m_ptr( + python::borrowed( + python::allow_null( // null check is already done + converter::arg_to_python(x).get()) + ) + ) + { + } - // capture this case explicitly to handle string - // literals. Otherwise, they get deduced as char[N]const& above - // and conversion fails at runtime. - explicit object(char const* x) - : m_ptr( - python::borrowed( - python::allow_null( // null check is already done - converter::arg_to_python(x).get()) - ) - ) - { + // capture this case explicitly to handle string + // literals. Otherwise, they get deduced as char[N]const& above + // and conversion fails at runtime. + explicit object(char const* x) + : m_ptr( + python::borrowed( + python::allow_null( // null check is already done + converter::arg_to_python(x).get()) + ) + ) + { - } + } - // Throw error_already_set() if the handle is null. - explicit object(handle<> const&); + // Throw error_already_set() if the handle is null. + explicit object(handle<> const&); - // Attribute access via x._("attribute_name") - const_object_attribute _(char const*) const; - object_attribute _(char const*); + // Attribute access via x.attr("attribute_name") + const_object_attribute attr(char const*) const; + object_attribute attr(char const*); - object operator()() const - { - return object(call >(m_ptr.get())); - } + object operator()() const + { + return object(call >(m_ptr.get())); + } # ifndef BOOST_PYTHON_GENERATE_CODE # include # endif # define BOOST_PYTHON_OBJECT_CALL(nargs,ignored) \ - template \ - object operator()(BOOST_PYTHON_ENUM_PARAMS2(nargs, (A,const& a))) const \ - { \ - return object(call >(&**this, BOOST_PP_ENUM_PARAMS(nargs, a))); \ - } + template \ + object operator()(BOOST_PYTHON_ENUM_PARAMS2(nargs, (A,const& a))) const \ + { \ + return object(call >(&**this, BOOST_PP_ENUM_PARAMS(nargs, a))); \ + } - BOOST_PYTHON_REPEAT_ARITY_2ND(BOOST_PYTHON_OBJECT_CALL, ignored) + BOOST_PYTHON_REPEAT_ARITY_2ND(BOOST_PYTHON_OBJECT_CALL, ignored) - // truth value testing - typedef handle<> (object::*bool_type); - operator bool_type() const; - bool operator!() const; // needed for vc6 + // truth value testing + typedef handle<> (object::*bool_type); + operator bool_type() const; + bool operator!() const; // needed for vc6 - // item access - const_object_item operator[](self_cref) const; - object_item operator[](self_cref); + // item access + const_object_item operator[](self_cref) const; + object_item operator[](self_cref); - template - const_object_item operator[](T const& key) const - { - return (*this)[object(key)]; - } + template +# if BOOST_MSVC != 1300 + typename dependent::type +# else + const_object_item +# endif + operator[](T const& key) const + { + return (*this)[object(key)]; + } - template - object_item operator[](T const& key) - { - return (*this)[object(key)]; - } + template +# if BOOST_MSVC != 1300 + typename dependent::type +# else + object_item +# endif + operator[](T const& key) + { + return (*this)[object(key)]; + } - // Underlying object access - PyObject* operator->() const; - PyObject& operator*() const; + // Underlying object access + PyObject* operator->() const; + PyObject& operator*() const; - public: // implementation detail -- for internal use only - object(null_ok >*); - object(detail::borrowed >*); - object(detail::borrowed*); - class new_pyobject_reference; - object(new_pyobject_reference*); + public: // implementation detail -- for internal use only + object(null_ok >*); + object(detail::borrowed >*); + object(detail::borrowed*); + class new_pyobject_reference; + object(new_pyobject_reference*); - private: - handle<> m_ptr; -}; + private: + handle<> m_ptr; + }; +} +using api::object; // // Converter Specializations diff --git a/include/boost/python/object_items.hpp b/include/boost/python/object_items.hpp index 27233c24..a294dff6 100755 --- a/include/boost/python/object_items.hpp +++ b/include/boost/python/object_items.hpp @@ -10,20 +10,17 @@ # include # include -namespace boost { namespace python { +namespace boost { namespace python { namespace api { -namespace detail +struct const_item_policies { - struct const_item_policies - { - static object get(object const& target, object const& key); - }; + static object get(object const& target, object const& key); +}; - struct item_policies : const_item_policies - { - static object const& set(object const& target, object const& key, object const& value); - }; -} +struct item_policies : const_item_policies +{ + static object const& set(object const& target, object const& key, object const& value); +}; // // implementation @@ -38,23 +35,23 @@ inline const_object_item object::operator[](object::self_cref key) const return const_object_item(*this, key); } -namespace detail -{ - inline object const_item_policies::get(object const& target, object const& key) - { - return python::getitem(target, key); - } - inline object const& item_policies::set( - object const& target - , object const& key - , object const& value) - { - python::setitem(target, key, value); - return value; - } +inline object const_item_policies::get(object const& target, object const& key) +{ + return python::getitem(target, key); } +inline object const& item_policies::set( + object const& target + , object const& key + , object const& value) +{ + python::setitem(target, key, value); + return value; +} + +} // namespace api + namespace converter { // These specializations are a lie; the proxies do not directly @@ -64,13 +61,13 @@ namespace converter // actually managing the object during the duration of the // conversion. template <> - struct is_object_manager + struct is_object_manager { BOOST_STATIC_CONSTANT(bool, value = true); }; template <> - struct is_object_manager + struct is_object_manager { BOOST_STATIC_CONSTANT(bool, value = true); }; diff --git a/include/boost/python/object_operators.hpp b/include/boost/python/object_operators.hpp new file mode 100644 index 00000000..c339c906 --- /dev/null +++ b/include/boost/python/object_operators.hpp @@ -0,0 +1,68 @@ +// Copyright David Abrahams 2002. Permission to copy, use, +// modify, sell and distribute this software is granted provided this +// copyright notice appears in all copies. This software is provided +// "as is" without express or implied warranty, and with no claim as +// to its suitability for any purpose. +#ifndef OBJECT_OPERATORS_DWA2002617_HPP +# define OBJECT_OPERATORS_DWA2002617_HPP + +# include + +namespace boost { namespace python { namespace api { + +# define BOOST_PYTHON_COMPARE_OP(op, opid) \ +template \ +bool operator op(L const& l, R const& r) \ +{ \ + return PyObject_RichCompareBool(&*object(l), &*object(r), opid); \ +} +BOOST_PYTHON_COMPARE_OP(>, Py_GT) +BOOST_PYTHON_COMPARE_OP(>=, Py_GE) +BOOST_PYTHON_COMPARE_OP(<, Py_LT) +BOOST_PYTHON_COMPARE_OP(<=, Py_LE) +BOOST_PYTHON_COMPARE_OP(==, Py_EQ) +BOOST_PYTHON_COMPARE_OP(!=, Py_NE) +# undef BOOST_PYTHON_COMPARE_OP + +# define BOOST_PYTHON_BINARY_OPERATOR(op) \ +BOOST_PYTHON_DECL object operator op(object const& l, object const& r); \ +template \ +object operator op(L const& l, R const& r) \ +{ \ + return object(l) op object(r); \ +} +BOOST_PYTHON_BINARY_OPERATOR(+) +BOOST_PYTHON_BINARY_OPERATOR(-) +BOOST_PYTHON_BINARY_OPERATOR(*) +BOOST_PYTHON_BINARY_OPERATOR(/) +BOOST_PYTHON_BINARY_OPERATOR(%) +BOOST_PYTHON_BINARY_OPERATOR(<<) +BOOST_PYTHON_BINARY_OPERATOR(>>) +BOOST_PYTHON_BINARY_OPERATOR(&) +BOOST_PYTHON_BINARY_OPERATOR(^) +BOOST_PYTHON_BINARY_OPERATOR(|) +# undef BOOST_PYTHON_BINARY_OPERATOR + + +# define BOOST_PYTHON_INPLACE_OPERATOR(op) \ +BOOST_PYTHON_DECL object& operator op(object& l, object const& r); \ +template \ +object& operator op(object& l, R const& r) \ +{ \ + return l op object(r); \ +} +BOOST_PYTHON_INPLACE_OPERATOR(+=) +BOOST_PYTHON_INPLACE_OPERATOR(-=) +BOOST_PYTHON_INPLACE_OPERATOR(*=) +BOOST_PYTHON_INPLACE_OPERATOR(/=) +BOOST_PYTHON_INPLACE_OPERATOR(%=) +BOOST_PYTHON_INPLACE_OPERATOR(<<=) +BOOST_PYTHON_INPLACE_OPERATOR(>>=) +BOOST_PYTHON_INPLACE_OPERATOR(&=) +BOOST_PYTHON_INPLACE_OPERATOR(^=) +BOOST_PYTHON_INPLACE_OPERATOR(|=) +# undef BOOST_PYTHON_INPLACE_OPERATOR + +}}} // namespace boost::python + +#endif // OBJECT_OPERATORS_DWA2002617_HPP diff --git a/include/boost/python/object_protocol_core.hpp b/include/boost/python/object_protocol_core.hpp index adbd593f..9fc28415 100755 --- a/include/boost/python/object_protocol_core.hpp +++ b/include/boost/python/object_protocol_core.hpp @@ -8,12 +8,20 @@ namespace boost { namespace python { -class object; +namespace api +{ + class object; -BOOST_PYTHON_DECL object getattr(object const& target, object const& key); -BOOST_PYTHON_DECL void setattr(object const& target, object const& key, object const& value); -BOOST_PYTHON_DECL object getitem(object const& target, object const& key); -BOOST_PYTHON_DECL void setitem(object const& target, object const& key, object const& value); + BOOST_PYTHON_DECL object getattr(object const& target, object const& key); + BOOST_PYTHON_DECL void setattr(object const& target, object const& key, object const& value); + BOOST_PYTHON_DECL object getitem(object const& target, object const& key); + BOOST_PYTHON_DECL void setitem(object const& target, object const& key, object const& value); +} + +using api::getattr; +using api::setattr; +using api::getitem; +using api::setitem; }} // namespace boost::python diff --git a/include/boost/python/operators2.hpp b/include/boost/python/operators2.hpp index a58b7559..d4bffb05 100755 --- a/include/boost/python/operators2.hpp +++ b/include/boost/python/operators2.hpp @@ -196,9 +196,12 @@ BOOST_PYTHON_BINARY_OPERATOR(lt, gt, <) BOOST_PYTHON_BINARY_OPERATOR(le, ge, <=) BOOST_PYTHON_BINARY_OPERATOR(eq, eq, ==) BOOST_PYTHON_BINARY_OPERATOR(ne, ne, !=) - +# undef BOOST_PYTHON_BINARY_OPERATOR + // pow isn't an operator in C++; handle it specially. BOOST_PYTHON_BINARY_OPERATION(pow, rpow, pow(l,r)) +# undef BOOST_PYTHON_BINARY_OPERATION + namespace self_ns { # ifndef BOOST_NO_ARGUMENT_DEPENDENT_LOOKUP @@ -300,6 +303,7 @@ namespace self_ns \ return detail::operator_(); \ } \ } +# undef BOOST_PYTHON_INPLACE_OPERATOR BOOST_PYTHON_UNARY_OPERATOR(neg, -, operator-) BOOST_PYTHON_UNARY_OPERATOR(pos, +, operator+) @@ -310,7 +314,8 @@ BOOST_PYTHON_UNARY_OPERATOR(long, PyLong_FromLong, long_) BOOST_PYTHON_UNARY_OPERATOR(float, double, float_) BOOST_PYTHON_UNARY_OPERATOR(complex, std::complex, complex_) BOOST_PYTHON_UNARY_OPERATOR(str, lexical_cast, str) - +# undef BOOST_PYTHON_UNARY_OPERATOR + }} // namespace boost::python # ifdef BOOST_NO_ARGUMENT_DEPENDENT_LOOKUP diff --git a/include/boost/python/proxy.hpp b/include/boost/python/proxy.hpp index 46476f2d..d7747c0d 100755 --- a/include/boost/python/proxy.hpp +++ b/include/boost/python/proxy.hpp @@ -6,10 +6,9 @@ #ifndef PROXY_DWA2002615_HPP # define PROXY_DWA2002615_HPP # include +# include -namespace boost { namespace python { - -class object; +namespace boost { namespace python { namespace api { template class proxy @@ -23,37 +22,15 @@ class proxy operator object() const; // to support a[b] = c[d] - proxy& operator=(copy_ctor_self); + proxy const& operator=(copy_ctor_self) const; template - inline proxy& operator=(T const& rhs) + inline proxy const& operator=(T const& rhs) const { Policies::set(m_target, m_key, python::object(rhs)); return *this; } -# define BOOST_PYTHON_PROXY_ASSIGN_DECL(op) \ - object operator op (object const&); \ - \ - template \ - object operator op (T const& rhs) \ - { \ - return *this op python::object(rhs); \ - } - -// BOOST_PYTHON_PROXY_ASSIGN_DECL(=) - BOOST_PYTHON_PROXY_ASSIGN_DECL(+=) - BOOST_PYTHON_PROXY_ASSIGN_DECL(-=) - BOOST_PYTHON_PROXY_ASSIGN_DECL(*=) - BOOST_PYTHON_PROXY_ASSIGN_DECL(/=) - BOOST_PYTHON_PROXY_ASSIGN_DECL(%=) - BOOST_PYTHON_PROXY_ASSIGN_DECL(<<=) - BOOST_PYTHON_PROXY_ASSIGN_DECL(>>=) - BOOST_PYTHON_PROXY_ASSIGN_DECL(&=) - BOOST_PYTHON_PROXY_ASSIGN_DECL(^=) - BOOST_PYTHON_PROXY_ASSIGN_DECL(|=) -# undef BOOST_PYTHON_PROXY_ASSIGN_DECL - // truth value testing operator object::bool_type() const; bool operator!() const; // needed for vc6 @@ -84,33 +61,29 @@ inline proxy::operator object() const // to support a[b] = c[d] template -inline proxy& proxy::operator=(typename proxy::copy_ctor_self rhs) +inline proxy const& proxy::operator=(typename proxy::copy_ctor_self rhs) const { return *this = python::object(rhs); } -# define BOOST_PYTHON_PROXY_ASSIGN_DEF(op) \ -template \ -inline object proxy::operator op(object const& other) \ -{ \ - return Policies::set( \ - m_target, m_key \ - , Policies::get(m_target,m_key) op other); \ -} - -BOOST_PYTHON_PROXY_ASSIGN_DEF(+=) -BOOST_PYTHON_PROXY_ASSIGN_DEF(-=) -BOOST_PYTHON_PROXY_ASSIGN_DEF(*=) -BOOST_PYTHON_PROXY_ASSIGN_DEF(/=) -BOOST_PYTHON_PROXY_ASSIGN_DEF(%=) -BOOST_PYTHON_PROXY_ASSIGN_DEF(<<=) -BOOST_PYTHON_PROXY_ASSIGN_DEF(>>=) -BOOST_PYTHON_PROXY_ASSIGN_DEF(&=) -BOOST_PYTHON_PROXY_ASSIGN_DEF(^=) -BOOST_PYTHON_PROXY_ASSIGN_DEF(|=) - -# undef BOOST_PYTHON_PROXY_ASSIGN_DEF - +# define BOOST_PYTHON_PROXY_INPLACE(op) \ +template \ +proxy const& operator op(proxy const& lhs, R const& other) \ +{ \ + object old(lhs); \ + return lhs = (old op other); \ +} +BOOST_PYTHON_PROXY_INPLACE(+=) +BOOST_PYTHON_PROXY_INPLACE(-=) +BOOST_PYTHON_PROXY_INPLACE(*=) +BOOST_PYTHON_PROXY_INPLACE(/=) +BOOST_PYTHON_PROXY_INPLACE(%=) +BOOST_PYTHON_PROXY_INPLACE(<<=) +BOOST_PYTHON_PROXY_INPLACE(>>=) +BOOST_PYTHON_PROXY_INPLACE(&=) +BOOST_PYTHON_PROXY_INPLACE(^=) +BOOST_PYTHON_PROXY_INPLACE(|=) +# undef BOOST_PYTHON_PROXY_INPLACE template inline proxy::operator object::bool_type() const @@ -124,6 +97,6 @@ inline bool proxy::operator!() const return !python::object(*this); } -}} // namespace boost::python +}}} // namespace boost::python::api #endif // PROXY_DWA2002615_HPP diff --git a/src/object_operators.cpp b/src/object_operators.cpp new file mode 100644 index 00000000..ed824ab6 --- /dev/null +++ b/src/object_operators.cpp @@ -0,0 +1,51 @@ +// Copyright David Abrahams 2002. Permission to copy, use, +// modify, sell and distribute this software is granted provided this +// copyright notice appears in all copies. This software is provided +// "as is" without express or implied warranty, and with no claim as +// to its suitability for any purpose. + +#include + +namespace boost { namespace python { namespace api { + +#define BOOST_PYTHON_BINARY_OPERATOR(op, name) \ +BOOST_PYTHON_DECL object operator op(object const& l, object const& r) \ +{ \ + return object( \ + (object::new_pyobject_reference*) \ + PyNumber_##name(&*l, &*r)); \ +} + +BOOST_PYTHON_BINARY_OPERATOR(+, Add) +BOOST_PYTHON_BINARY_OPERATOR(-, Subtract) +BOOST_PYTHON_BINARY_OPERATOR(*, Multiply) +BOOST_PYTHON_BINARY_OPERATOR(/, Divide) +BOOST_PYTHON_BINARY_OPERATOR(%, Remainder) +BOOST_PYTHON_BINARY_OPERATOR(<<, Lshift) +BOOST_PYTHON_BINARY_OPERATOR(>>, Rshift) +BOOST_PYTHON_BINARY_OPERATOR(&, And) +BOOST_PYTHON_BINARY_OPERATOR(^, Xor) +BOOST_PYTHON_BINARY_OPERATOR(|, Or) +#undef BOOST_PYTHON_BINARY_OPERATOR + +#define BOOST_PYTHON_INPLACE_OPERATOR(op, name) \ +BOOST_PYTHON_DECL object& operator op##=(object& l, object const& r) \ +{ \ + return l = object( \ + (object::new_pyobject_reference*) \ + PyNumber_InPlace##name(&*l, &*r)); \ +} + +BOOST_PYTHON_INPLACE_OPERATOR(+, Add) +BOOST_PYTHON_INPLACE_OPERATOR(-, Subtract) +BOOST_PYTHON_INPLACE_OPERATOR(*, Multiply) +BOOST_PYTHON_INPLACE_OPERATOR(/, Divide) +BOOST_PYTHON_INPLACE_OPERATOR(%, Remainder) +BOOST_PYTHON_INPLACE_OPERATOR(<<, Lshift) +BOOST_PYTHON_INPLACE_OPERATOR(>>, Rshift) +BOOST_PYTHON_INPLACE_OPERATOR(&, And) +BOOST_PYTHON_INPLACE_OPERATOR(^, Xor) +BOOST_PYTHON_INPLACE_OPERATOR(|, Or) +#undef BOOST_PYTHON_INPLACE_OPERATOR + +}}} // namespace boost::python diff --git a/src/object_protocol.cpp b/src/object_protocol.cpp index c04a8da2..8f2548ab 100755 --- a/src/object_protocol.cpp +++ b/src/object_protocol.cpp @@ -8,28 +8,28 @@ #include #include -namespace boost { namespace python { +namespace boost { namespace python { namespace api { -object getattr(object const& target, object const& key) +BOOST_PYTHON_DECL object getattr(object const& target, object const& key) { return object((object::new_pyobject_reference*)PyObject_GetAttr(&*target, &*key)); } -void setattr(object const& target, object const& key, object const& value) +BOOST_PYTHON_DECL void setattr(object const& target, object const& key, object const& value) { if (PyObject_SetAttr(&*target, &*key, &*value) == -1) throw_error_already_set(); } -object getitem(object const& target, object const& key) +BOOST_PYTHON_DECL object getitem(object const& target, object const& key) { return object((object::new_pyobject_reference*)PyObject_GetItem(&*target, &*key)); } -void setitem(object const& target, object const& key, object const& value) +BOOST_PYTHON_DECL void setitem(object const& target, object const& key, object const& value) { if (PyObject_SetItem(&*target, &*key, &*value) == -1) throw_error_already_set(); } -}} // namespace boost::python +}}} // namespace boost::python::api diff --git a/test/doctest.py b/test/doctest.py index 248da82a..2829f1e6 100644 --- a/test/doctest.py +++ b/test/doctest.py @@ -1,10 +1,10 @@ -# Module doctest version 0.9.4 -# Released to the public domain 27-Mar-1999, -# by Tim Peters (tim_one@email.msn.com). +# Module doctest. +# Released to the public domain 16-Jan-2001, +# by Tim Peters (tim.one@home.com). # Provided as-is; use at your own risk; no warranty; no promises; enjoy! -"""module_builder doctest -- a framework for running examples in docstrings. +"""Module doctest -- a framework for running examples in docstrings. NORMAL USAGE @@ -20,16 +20,16 @@ if __name__ == "__main__": Then running the module as a script will cause the examples in the docstrings to get executed and verified: -python M.python +python M.py -This won't display anything unless an example fails, in which case -the failing example(s) and the cause(s) of the failure(s) are printed -to stdout (why not stderr? because stderr is a lame hack <0.2 wink>), -and the final line of output is "Test failed.". +This won't display anything unless an example fails, in which case the +failing example(s) and the cause(s) of the failure(s) are printed to stdout +(why not stderr? because stderr is a lame hack <0.2 wink>), and the final +line of output is "Test failed.". Run it with the -v switch instead: -python M.python -v +python M.py -v and a detailed report of all examples tried is printed to stdout, along with assorted summaries at the end. @@ -48,71 +48,52 @@ WHICH DOCSTRINGS ARE EXAMINED? + M.__doc__. + f.__doc__ for all functions f in M.__dict__.values(), except those - with private names. + with private names and those defined in other modules. + C.__doc__ for all classes C in M.__dict__.values(), except those with - private names. + private names and those defined in other modules. + If M.__test__ exists and "is true", it must be a dict, and each entry maps a (string) name to a function object, class object, or - string. function and class object docstrings found from M.__test__ + string. Function and class object docstrings found from M.__test__ are searched even if the name is private, and strings are searched directly as if they were docstrings. In output, a key K in M.__test__ appears with name .__test__.K -Any classes found are recursively searched similarly, to test docstrings -in their contained methods and nested classes. Private names reached -from M's globals are skipped, but all names reached from M.__test__ are -searched. +Any classes found are recursively searched similarly, to test docstrings in +their contained methods and nested classes. Private names reached from M's +globals are skipped, but all names reached from M.__test__ are searched. By default, a name is considered to be private if it begins with an -underscore (like "_my_func") but doesn't both begin and end with (at -least) two underscores (like "__init__"). You can change the default -by passing your own "isprivate" function to testmod. +underscore (like "_my_func") but doesn't both begin and end with (at least) +two underscores (like "__init__"). You can change the default by passing +your own "isprivate" function to testmod. If you want to test docstrings in objects with private names too, stuff them into an M.__test__ dict, or see ADVANCED USAGE below (e.g., pass your own isprivate function to Tester's constructor, or call the rundoc method -of a Tester obj). - -Warning: imports can cause trouble; e.g., if you do - -from XYZ import XYZclass - -then XYZclass is a name in M.__dict__ too, and doctest has no way to -know that XYZclass wasn't *defined* in M. So it may try to execute the -examples in XYZclass's docstring, and those in turn may require a -different set of globals to work correctly. I prefer to do "import *"- -friendly imports, a la - -import XYY -_XYZclass = XYZ.XYZclass -del XYZ - -and then the leading underscore stops testmod from going nuts. You may -prefer the method in the next section. - +of a Tester instance). WHAT'S THE EXECUTION CONTEXT? -By default, each time testmod finds a docstring to test, it uses a -*copy* of M's globals (so that running tests on a module doesn't change -the module's real globals, and so that one test in M can't leave behind -crumbs that accidentally allow another test to work). This means -examples can freely use any names defined at top-level in M. It also -means that sloppy imports (see above) can cause examples in external -docstrings to use globals inappropriate for them. +By default, each time testmod finds a docstring to test, it uses a *copy* +of M's globals (so that running tests on a module doesn't change the +module's real globals, and so that one test in M can't leave behind crumbs +that accidentally allow another test to work). This means examples can +freely use any names defined at top-level in M. It also means that sloppy +imports (see above) can cause examples in external docstrings to use +globals inappropriate for them. You can force use of your own dict as the execution context by passing -"globs=your_dict" to testmod instead. Presumably this would be a copy -of M.__dict__ merged with the globals from other imported modules. +"globs=your_dict" to testmod instead. Presumably this would be a copy of +M.__dict__ merged with the globals from other imported modules. WHAT IF I WANT TO TEST A WHOLE PACKAGE? Piece o' cake, provided the modules do their testing from docstrings. -Here's the test.python I use for the world's most elaborate Rational/ +Here's the test.py I use for the world's most elaborate Rational/ floating-base-conversion pkg (which I'll distribute some day): from Rational import Cvt @@ -141,11 +122,10 @@ if __name__ == "__main__": _test() IOW, it just runs testmod on all the pkg modules. testmod remembers the -names and outcomes (# of failures, # of tries) for each item it's seen, -and passing "report=0" prevents it from printing a summary in verbose -mode. Instead, the summary is delayed until all modules have been -tested, and then "doctest.master.summarize()" forces the summary at the -end. +names and outcomes (# of failures, # of tries) for each item it's seen, and +passing "report=0" prevents it from printing a summary in verbose mode. +Instead, the summary is delayed until all modules have been tested, and +then "doctest.master.summarize()" forces the summary at the end. So this is very nice in practice: each module can be tested individually with almost no work beyond writing up docstring examples, and collections @@ -157,10 +137,10 @@ WHAT ABOUT EXCEPTIONS? No problem, as long as the only output generated by the example is the traceback itself. For example: - >>> 1/0 - Traceback (innermost last): + >>> [1, 2, 3].remove(42) + Traceback (most recent call last): File "", line 1, in ? - ZeroDivisionError: integer division or modulo + ValueError: list.remove(x): x not in list >>> Note that only the exception type and value are compared (specifically, @@ -172,22 +152,22 @@ ADVANCED USAGE doctest.testmod() captures the testing policy I find most useful most often. You may want other policies. -testmod() actually creates a local obj of class doctest.Tester, -runs appropriate methods of that class, and merges the results into -global Tester obj doctest.master. +testmod() actually creates a local instance of class doctest.Tester, runs +appropriate methods of that class, and merges the results into global +Tester instance doctest.master. -You can create your own instances of doctest.Tester, and so build your -own policies, or even run methods of doctest.master directly. See +You can create your own instances of doctest.Tester, and so build your own +policies, or even run methods of doctest.master directly. See doctest.Tester.__doc__ for details. SO WHAT DOES A DOCSTRING EXAMPLE LOOK LIKE ALREADY!? Oh ya. It's easy! In most cases a copy-and-paste of an interactive -console session works fine -- just make sure the leading whitespace -is rigidly consistent (you can mix tabs and spaces if you're too lazy -to do it right, but doctest is not in the business of guessing what -you think a tab means). +console session works fine -- just make sure the leading whitespace is +rigidly consistent (you can mix tabs and spaces if you're too lazy to do it +right, but doctest is not in the business of guessing what you think a tab +means). >>> # comments are ignored >>> x = 12 @@ -205,23 +185,22 @@ you think a tab means). NO!!! >>> -Any expected output must immediately follow the final ">>>" or "..." -line containing the code, and the expected output (if any) extends -to the next ">>>" or all-whitespace line. That's it. +Any expected output must immediately follow the final ">>>" or "..." line +containing the code, and the expected output (if any) extends to the next +">>>" or all-whitespace line. That's it. Bummers: -+ Expected output cannot contain an all-whitespace line, since such a - line is taken to signal the end of expected output. ++ Expected output cannot contain an all-whitespace line, since such a line + is taken to signal the end of expected output. + Output to stdout is captured, but not output to stderr (exception tracebacks are captured via a different means). -+ If you continue a line via backslashing in an interactive session, - or for any other reason use a backslash, you need to double the - backslash in the docstring version. This is simply because you're - in a string, and so the backslash must be escaped for it to survive - intact. Like: ++ If you continue a line via backslashing in an interactive session, or for + any other reason use a backslash, you need to double the backslash in the + docstring version. This is simply because you're in a string, and so the + backslash must be escaped for it to survive intact. Like: >>> if "yes" == \\ ... "y" + \\ @@ -243,11 +222,11 @@ If you execute this very file, the examples above will be found and executed, leading to this output in verbose mode: Running doctest.__doc__ -Trying: 1/0 +Trying: [1, 2, 3].remove(42) Expecting: -Traceback (innermost last): +Traceback (most recent call last): File "", line 1, in ? -ZeroDivisionError: integer division or modulo +ValueError: list.remove(x): x not in list ok Trying: x = 12 Expecting: nothing @@ -279,7 +258,7 @@ ok 8 tests in doctest 6 tests in doctest.Tester 10 tests in doctest.Tester.merge - 7 tests in doctest.Tester.rundict + 14 tests in doctest.Tester.rundict 3 tests in doctest.Tester.rundoc 3 tests in doctest.Tester.runstring 2 tests in doctest.__test__._TestClass @@ -288,79 +267,19 @@ ok 1 tests in doctest.__test__._TestClass.square 2 tests in doctest.__test__.string 7 tests in doctest.is_private -53 tests in 17 items. -53 passed and 0 failed. +60 tests in 17 items. +60 passed and 0 failed. Test passed. """ -# 0,0,1 06-Mar-1999 -# initial version posted -# 0,0,2 06-Mar-1999 -# loosened parsing: -# cater to stinkin' tabs -# don't insist on a blank after PS2 prefix -# so trailing "... " line from a compound stmt no longer -# breaks if the file gets whitespace-trimmed -# better error msgs for inconsistent leading whitespace -# 0,9,1 08-Mar-1999 -# exposed the Tester class and added client methods -# plus docstring examples of their use (eww - head-twisting!) -# fixed logic error in reporting total # of tests & failures -# added __test__ support to testmod (a pale reflection of Christian -# Tismer's vision ...) -# removed the "deep" argument; fiddle __test__ instead -# simplified endcase logic for extracting tests, and running them. -# before, if no output was expected but some was produced -# anyway via an eval'ed result, the discrepancy wasn't caught -# made TestClass private and used __test__ to get at it -# many doc updates -# speed _SpoofOut for long expected outputs -# 0,9,2 09-Mar-1999 -# throw out comments from examples, enabling use of the much simpler -# exec compile(... "single") ... -# for simulating the runtime; that barfs on comment-only lines -# used the traceback module to do a much better job of reporting -# exceptions -# run __doc__ values thru str(), "just in case" -# privateness of names now determined by an overridable "isprivate" -# function -# by default a name now considered to be private iff it begins with -# an underscore but doesn't both begin & end with two of 'em; so -# e.g. class_t.__init__ etc are searched now -- as they always -# should have been -# 0,9,3 18-Mar-1999 -# added .flush stub to _SpoofOut (JPython buglet diagnosed by -# Hugh Emberson) -# repaired ridiculous docs about backslashes in examples -# minor internal changes -# changed source to Unix line-end conventions -# moved __test__ logic into new Tester.run__test__ method -# 0,9,4 27-Mar-1999 -# report item name and line # in failing examples -# 0,9,5 29-Jun-1999 -# allow straightforward exceptions in examples - thanks to Mark Hammond! -# 0,9,5,1 31-Mar-2000 -# break cyclic references to functions which are defined in docstrings, -# avoiding cyclic trash -# 0,9,5,2 11-Apr-2000 -# made module argument to testmod optional; it runs testmod on the __main__ -# module in that case. +__all__ = [ + 'testmod', + 'run_docstring_examples', + 'is_private', + 'Tester', +] -__version__ = 0, 9, 5 - -import types -_FunctionType = types.FunctionType -_ClassType = types.ClassType -_ModuleType = types.ModuleType -_StringType = types.StringType -del types - -import string -_string_find = string.find -_string_join = string.join -_string_split = string.split -_string_rindex = string.rindex -del string +import __future__ import re PS1 = ">>>" @@ -371,6 +290,13 @@ _isEmpty = re.compile(r"\s*$").match _isComment = re.compile(r"\s*#").match del re +from types import StringTypes as _StringTypes + +from inspect import isclass as _isclass +from inspect import isfunction as _isfunction +from inspect import ismodule as _ismodule +from inspect import classify_class_attrs as _classify_class_attrs + # Extract interactive examples from a string. Return a list of triples, # (source, outcome, lineno). "source" is the source code, and ends # with a newline iff the source spans more than one line. "outcome" is @@ -382,7 +308,7 @@ def _extract_examples(s): isPS1, isPS2 = _isPS1, _isPS2 isEmpty, isComment = _isEmpty, _isComment examples = [] - lines = _string_split(s, "\n") + lines = s.split("\n") i, n = 0, len(lines) while i < n: line = lines[i] @@ -420,7 +346,7 @@ def _extract_examples(s): # get rid of useless null line from trailing empty "..." if source[-1] == "": del source[-1] - source = _string_join(source, "\n") + "\n" + source = "\n".join(source) + "\n" # suck up response if isPS1(line) or isEmpty(line): expect = "" @@ -435,7 +361,7 @@ def _extract_examples(s): line = lines[i] if isPS1(line) or isEmpty(line): break - expect = _string_join(expect, "\n") + "\n" + expect = "\n".join(expect) + "\n" examples.append( (source, expect, lineno) ) return examples @@ -447,9 +373,21 @@ class _SpoofOut: def write(self, s): self.buf.append(s) def get(self): - return _string_join(self.buf, "") + guts = "".join(self.buf) + # If anything at all was written, make sure there's a trailing + # newline. There's no way for the expected output to indicate + # that a trailing newline is missing. + if guts and not guts.endswith("\n"): + guts = guts + "\n" + # Prevent softspace from screwing up the next test case, in + # case they used print with a trailing comma in an example. + if hasattr(self, "softspace"): + del self.softspace + return guts def clear(self): self.buf = [] + if hasattr(self, "softspace"): + del self.softspace def flush(self): # JPython calls flush pass @@ -462,7 +400,7 @@ def _tag_out(printer, *tag_msg_pairs): printer(tag + ":") msg_has_nl = msg[-1:] == "\n" msg_has_two_nl = msg_has_nl and \ - _string_find(msg, "\n") < len(msg) - 1 + msg.find("\n") < len(msg) - 1 if len(tag) + len(msg) < 76 and not msg_has_two_nl: printer(" ") else: @@ -472,10 +410,11 @@ def _tag_out(printer, *tag_msg_pairs): printer("\n") # Run list of examples, in context globs. "out" can be used to display -# stuff to "the real" stdout, and fakeout is an obj of _SpoofOut +# stuff to "the real" stdout, and fakeout is an instance of _SpoofOut # that captures the examples' std output. Return (#failures, #tries). -def _run_examples_inner(out, fakeout, examples, globs, verbose, name): +def _run_examples_inner(out, fakeout, examples, globs, verbose, name, + compileflags): import sys, traceback OK, BOOM, FAIL = range(3) NADA = "nothing" @@ -487,17 +426,19 @@ def _run_examples_inner(out, fakeout, examples, globs, verbose, name): ("Expecting", want or NADA)) fakeout.clear() try: - exec compile(source, "", "single") in globs + exec compile(source, "", "single", + compileflags, 1) in globs got = fakeout.get() state = OK except: # See whether the exception was expected. - if _string_find(want, "Traceback (innermost last):\n") == 0: + if want.find("Traceback (innermost last):\n") == 0 or \ + want.find("Traceback (most recent call last):\n") == 0: # Only compare exception type and value - the rest of # the traceback isn't necessary. - want = _string_split(want, '\n')[-2] + '\n' - exc_type, exc_val, exc_tb = sys.exc_info() - got = traceback.format_exception_only(exc_type, exc_val)[0] + want = want.split('\n')[-2] + '\n' + exc_type, exc_val = sys.exc_info()[:2] + got = traceback.format_exception_only(exc_type, exc_val)[-1] state = OK else: # unexpected exception @@ -522,25 +463,49 @@ def _run_examples_inner(out, fakeout, examples, globs, verbose, name): else: assert state == BOOM _tag_out(out, ("Exception raised", stderr.get())) + return failures, len(examples) -# Run list of examples, in context globs. Return (#failures, #tries). +# Get the future-flags associated with the future features that have been +# imported into globs. -def _run_examples(examples, globs, verbose, name): +def _extract_future_flags(globs): + flags = 0 + for fname in __future__.all_feature_names: + feature = globs.get(fname, None) + if feature is getattr(__future__, fname): + flags |= feature.compiler_flag + return flags + +# Run list of examples, in a shallow copy of context (dict) globs. +# Return (#failures, #tries). + +def _run_examples(examples, globs, verbose, name, compileflags): import sys saveout = sys.stdout + globs = globs.copy() try: sys.stdout = fakeout = _SpoofOut() x = _run_examples_inner(saveout.write, fakeout, examples, - globs, verbose, name) + globs, verbose, name, compileflags) finally: sys.stdout = saveout + # While Python gc can clean up most cycles on its own, it doesn't + # chase frame objects. This is especially irksome when running + # generator tests that raise exceptions, because a named generator- + # iterator gets an entry in globs, and the generator-iterator + # object's frame's traceback info points back to globs. This is + # easy to break just by clearing the namespace. This can also + # help to break other kinds of cycles, and even for cycles that + # gc can break itself it's better to break them ASAP. + globs.clear() return x -def run_docstring_examples(f, globs, verbose=0, name="NoName"): +def run_docstring_examples(f, globs, verbose=0, name="NoName", + compileflags=None): """f, globs, verbose=0, name="NoName" -> run examples from f.__doc__. - Use dict globs as the globals for execution. + Use (a shallow copy of) dict globs as the globals for execution. Return (#failures, #tries). If optional arg verbose is true, print stuff even if there are no @@ -562,7 +527,9 @@ def run_docstring_examples(f, globs, verbose=0, name="NoName"): e = _extract_examples(doc) if not e: return 0, 0 - return _run_examples(e, globs, verbose, name) + if compileflags is None: + compileflags = _extract_future_flags(globs) + return _run_examples(e, globs, verbose, name, compileflags) def is_private(prefix, base): """prefix, base -> true iff name prefix + "." + base is "private". @@ -591,8 +558,17 @@ def is_private(prefix, base): return base[:1] == "_" and not base[:2] == "__" == base[-2:] +# Determine if a class of function was defined in the given module. + +def _from_module(module, object): + if _isfunction(object): + return module.__dict__ is object.func_globals + if _isclass(object): + return module.__name__ == object.__module__ + raise ValueError("object must be a class or function") + class Tester: - """class_t Tester -- runs docstring examples and accumulates stats. + """Class Tester -- runs docstring examples and accumulates stats. In normal use, function doctest.testmod() hides all this from you, so use that if you can. Create your own instances of Tester to do @@ -607,9 +583,10 @@ Methods: Search object.__doc__ for examples to run; use name (or object.__name__) for logging. Return (#failures, #tries). - rundict(d, name) + rundict(d, name, module=None) Search for examples in docstrings in all of d.values(); use name - for logging. Return (#failures, #tries). + for logging. Exclude functions and classes not defined in module + if specified. Return (#failures, #tries). run__test__(d, name) Treat dict d like module.__test__. Return (#failures, #tries). @@ -619,7 +596,7 @@ Methods: (#failures, #tries). merge(other) - Merge in the test results from Tester obj "other". + Merge in the test results from Tester instance "other". >>> from doctest import Tester >>> t = Tester(globs={'x': 42}, verbose=0) @@ -637,6 +614,7 @@ Got: 84 >>> t.runstring(">>> x = x * 2\\n>>> print x\\n84\\n", 'example2') (0, 2) >>> t.summarize() +***************************************************************** 1 items had failures: 1 of 2 in XYZ ***Test Failed*** 1 failures. @@ -644,6 +622,7 @@ Got: 84 >>> t.summarize(verbose=1) 1 items passed all tests: 2 tests in example2 +***************************************************************** 1 items had failures: 1 of 2 in XYZ 4 tests in 2 items. @@ -679,7 +658,7 @@ see its docs for details. if mod is None and globs is None: raise TypeError("Tester.__init__: must specify mod or globs") - if mod is not None and type(mod) is not _ModuleType: + if mod is not None and not _ismodule(mod): raise TypeError("Tester.__init__: mod must be a module; " + `mod`) if globs is None: @@ -697,6 +676,8 @@ see its docs for details. self.name2ft = {} # map name to (#failures, #trials) pair + self.compileflags = _extract_future_flags(globs) + def runstring(self, s, name): """ s, name -> search string s for examples to run, logging as name. @@ -728,9 +709,8 @@ see its docs for details. f = t = 0 e = _extract_examples(s) if e: - globs = self.globs.copy() - f, t = _run_examples(e, globs, self.verbose, name) - globs.clear() # DWA - break cyclic references to functions defined in docstrings + f, t = _run_examples(e, self.globs, self.verbose, name, + self.compileflags) if self.verbose: print f, "of", t, "examples failed in string", name self.__record_outcome(name, f, t) @@ -768,51 +748,133 @@ see its docs for details. "when object.__name__ doesn't exist; " + `object`) if self.verbose: print "Running", name + ".__doc__" - globs = self.globs.copy() - f, t = run_docstring_examples(object, globs, - self.verbose, name) - globs.clear() # DWA - break cyclic references to functions defined in docstrings - + f, t = run_docstring_examples(object, self.globs, self.verbose, name, + self.compileflags) if self.verbose: print f, "of", t, "examples failed in", name + ".__doc__" self.__record_outcome(name, f, t) - if type(object) is _ClassType: - f2, t2 = self.rundict(object.__dict__, name) - f = f + f2 - t = t + t2 + if _isclass(object): + # In 2.2, class and static methods complicate life. Build + # a dict "that works", by hook or by crook. + d = {} + for tag, kind, homecls, value in _classify_class_attrs(object): + + if homecls is not object: + # Only look at names defined immediately by the class. + continue + + elif self.isprivate(name, tag): + continue + + elif kind == "method": + # value is already a function + d[tag] = value + + elif kind == "static method": + # value isn't a function, but getattr reveals one + d[tag] = getattr(object, tag) + + elif kind == "class method": + # Hmm. A classmethod object doesn't seem to reveal + # enough. But getattr turns it into a bound method, + # and from there .im_func retrieves the underlying + # function. + d[tag] = getattr(object, tag).im_func + + elif kind == "property": + # The methods implementing the property have their + # own docstrings -- but the property may have one too. + if value.__doc__ is not None: + d[tag] = str(value.__doc__) + + elif kind == "data": + # Grab nested classes. + if _isclass(value): + d[tag] = value + + else: + raise ValueError("teach doctest about %r" % kind) + + f2, t2 = self.run__test__(d, name) + f += f2 + t += t2 + return f, t - def rundict(self, d, name): + def rundict(self, d, name, module=None): """ - d. name -> search for docstring examples in all of d.values(). + d, name, module=None -> search for docstring examples in d.values(). For k, v in d.items() such that v is a function or class, do self.rundoc(v, name + "." + k). Whether this includes objects with private names depends on the constructor's - "isprivate" argument. + "isprivate" argument. If module is specified, functions and + classes that are not defined in module are excluded. Return aggregate (#failures, #examples). - >>> def _f(): - ... '''>>> assert 1 == 1 - ... ''' - >>> def g(): + Build and populate two modules with sample functions to test that + exclusion of external functions and classes works. + + >>> import new + >>> m1 = new.module('_m1') + >>> m2 = new.module('_m2') + >>> test_data = \""" + ... def _f(): + ... '''>>> assert 1 == 1 + ... ''' + ... def g(): ... '''>>> assert 2 != 1 ... ''' - >>> d = {"_f": _f, "g": g} + ... class H: + ... '''>>> assert 2 > 1 + ... ''' + ... def bar(self): + ... '''>>> assert 1 < 2 + ... ''' + ... \""" + >>> exec test_data in m1.__dict__ + >>> exec test_data in m2.__dict__ + >>> m1.__dict__.update({"f2": m2._f, "g2": m2.g, "h2": m2.H}) + + Tests that objects outside m1 are excluded: + >>> t = Tester(globs={}, verbose=0) - >>> t.rundict(d, "rundict_test") # _f is skipped - (0, 1) + >>> t.rundict(m1.__dict__, "rundict_test", m1) # _f, f2 and g2 and h2 skipped + (0, 3) + + Again, but with a custom isprivate function allowing _f: + >>> t = Tester(globs={}, verbose=0, isprivate=lambda x,y: 0) - >>> t.rundict(d, "rundict_test_pvt") # both are searched - (0, 2) + >>> t.rundict(m1.__dict__, "rundict_test_pvt", m1) # Only f2, g2 and h2 skipped + (0, 4) + + And once more, not excluding stuff outside m1: + + >>> t = Tester(globs={}, verbose=0, isprivate=lambda x,y: 0) + >>> t.rundict(m1.__dict__, "rundict_test_pvt") # None are skipped. + (0, 8) + + The exclusion of objects from outside the designated module is + meant to be invoked automagically by testmod. + + >>> testmod(m1) + (0, 3) + """ if not hasattr(d, "items"): raise TypeError("Tester.rundict: d must support .items(); " + `d`) f = t = 0 - for thisname, value in d.items(): - if type(value) in (_FunctionType, _ClassType): + # Run the tests by alpha order of names, for consistency in + # verbose-mode output. + names = d.keys() + names.sort() + for thisname in names: + value = d[thisname] + if _isfunction(value) or _isclass(value): + if module and not _from_module(module, value): + continue f2, t2 = self.__runone(value, name + "." + thisname) f = f + f2 t = t + t2 @@ -830,11 +892,16 @@ see its docs for details. savepvt = self.isprivate try: self.isprivate = lambda *args: 0 - for k, v in d.items(): + # Run the tests by alpha order of names, for consistency in + # verbose-mode output. + keys = d.keys() + keys.sort() + for k in keys: + v = d[k] thisname = prefix + k - if type(v) is _StringType: + if type(v) in _StringTypes: f, t = self.runstring(v, thisname) - elif type(v) in (_FunctionType, _ClassType): + elif _isfunction(v) or _isclass(v): f, t = self.rundoc(v, thisname) else: raise TypeError("Tester.run__test__: values in " @@ -885,6 +952,7 @@ see its docs for details. for thing, count in passed: print " %3d tests in %s" % (count, thing) if failed: + print "*" * 65 print len(failed), "items had failures:" failed.sort() for thing, (f, t) in failed: @@ -900,7 +968,7 @@ see its docs for details. def merge(self, other): """ - other -> merge in test results from the other Tester obj. + other -> merge in test results from the other Tester instance. If self and other both have a test result for something with the same name, the (#failures, #tests) results are @@ -962,7 +1030,7 @@ see its docs for details. def __runone(self, target, name): if "." in name: - i = _string_rindex(name, ".") + i = name.rindex(".") prefix, base = name[:i], name[i+1:] else: prefix, base = "", base @@ -972,9 +1040,9 @@ see its docs for details. master = None -def testmod(m=None, name=None, globs=None, verbose=None, isprivate=None, +def testmod(m, name=None, globs=None, verbose=None, isprivate=None, report=1): - """m=None, name=None, globs=None, verbose=None, isprivate=None, report=1 + """m, name=None, globs=None, verbose=None, isprivate=None, report=1 Test examples in docstrings in functions and classes reachable from module m, starting with m.__doc__. Private names are skipped. @@ -1007,10 +1075,10 @@ def testmod(m=None, name=None, globs=None, verbose=None, isprivate=None, else prints nothing at the end. In verbose mode, the summary is detailed, else very brief (in fact, empty if all tests passed). - Advanced tomfoolery: testmod runs methods of a local obj of + Advanced tomfoolery: testmod runs methods of a local instance of class doctest.Tester, then merges the results into (or creates) - global Tester obj doctest.master. Methods of doctest.master - can be called directly too, if you want to do something unusual. + global Tester instance doctest.master. Methods of doctest.master + can be called directly too, if you want to do something unusual. Passing report=0 to testmod is especially useful then, to delay displaying a summary. Invoke doctest.master.summarize(verbose) when you're done fiddling. @@ -1018,20 +1086,13 @@ def testmod(m=None, name=None, globs=None, verbose=None, isprivate=None, global master - if m is None: - import sys - # DWA - m will still be None if this wasn't invoked from the command - # line, in which case the following TypeError is about as good an error - # as we should expect - m = sys.modules.get('__main__') - - if type(m) is not _ModuleType: + if not _ismodule(m): raise TypeError("testmod: module required; " + `m`) if name is None: name = m.__name__ tester = Tester(m, globs=globs, verbose=verbose, isprivate=isprivate) failures, tries = tester.rundoc(m, name) - f, t = tester.rundict(m.__dict__, name) + f, t = tester.rundict(m.__dict__, name, m) failures = failures + f tries = tries + t if hasattr(m, "__test__"): diff --git a/test/object.cpp b/test/object.cpp index f3a31011..9fb847d1 100755 --- a/test/object.cpp +++ b/test/object.cpp @@ -25,27 +25,27 @@ object number() object obj_getattr(object x, char const* name) { - return x._(name); + return x.attr(name); } object obj_const_getattr(object const& x, char const* name) { - return x._(name); + return x.attr(name); } void obj_setattr(object x, char const* name, object value) { - x._(name) = value; + x.attr(name) = value; } void obj_setattr42(object x, char const* name) { - x._(name) = 42; + x.attr(name) = 42; } void obj_moveattr(object& x, char const* src, char const* dst) { - x._(dst) = x._(src); + x.attr(dst) = x.attr(src); } object obj_getitem(object x, object key) @@ -95,12 +95,12 @@ bool test_not(object y) bool test_attr(object y, char* name) { - return y._(name); + return y.attr(name); } bool test_not_attr(object y, char* name) { - return !y._(name); + return !y.attr(name); } bool test_item(object y, object key) @@ -113,20 +113,161 @@ bool test_not_item(object y, object key) return !y[key]; } +bool check_binary_operators() +{ + int y; + + object x(3); + +#define TEST_BINARY(op) \ + for (y = 1; y < 6; ++y) \ + { \ + if ((x op y) != (3 op y)) \ + return false; \ + } \ + for (y = 1; y < 6; ++y) \ + { \ + if ((y op x) != (y op 3)) \ + return false; \ + } \ + for (y = 1; y < 6; ++y) \ + { \ + object oy(y); \ + if ((oy op x) != (oy op 3)) \ + return false; \ + } + TEST_BINARY(>) + TEST_BINARY(>=) + TEST_BINARY(<) + TEST_BINARY(<=) + TEST_BINARY(==) + TEST_BINARY(!=) + + TEST_BINARY(+) + TEST_BINARY(-) + TEST_BINARY(*) + TEST_BINARY(/) + TEST_BINARY(%) + TEST_BINARY(<<) + TEST_BINARY(>>) + TEST_BINARY(&) + TEST_BINARY(^) + TEST_BINARY(|) + return true; +} + +bool check_inplace(object l, object o) +{ + int y; +#define TEST_INPLACE(op) \ + for (y = 1; y < 6; ++y) \ + { \ + object x(666); \ + x op##= y; \ + if (x != (666 op y)) \ + return false; \ + } \ + for (y = 1; y < 6; ++y) \ + { \ + object x(666); \ + x op##= object(y); \ + if (!(x == (666 op y))) \ + return false; \ + } + TEST_INPLACE(+) + TEST_INPLACE(-) + TEST_INPLACE(*) + TEST_INPLACE(/) + TEST_INPLACE(%) + TEST_INPLACE(<<) + TEST_INPLACE(>>) + TEST_INPLACE(&) + TEST_INPLACE(^) + TEST_INPLACE(|) + + l += l; + for (y = 0; y < 6; ++y) + { + if (l[y] != y % 3) + return false; + } + +#define TEST_ITEM_INPLACE(index, op, n, r1, r2) \ + l[index] op##= n; \ + if (l[index] != r1) \ + return false; \ + l[index] op##= object(n); \ + if (!(l[index] == r2)) \ + return false; + + TEST_ITEM_INPLACE(0,+,7,7,14) + TEST_ITEM_INPLACE(1,-,2,-1,-3) + TEST_ITEM_INPLACE(2,*,3,6,18) + TEST_ITEM_INPLACE(2,/,2,9,4) + TEST_ITEM_INPLACE(0,%,4,2,2) + l[0] += 1; + TEST_ITEM_INPLACE(0,<<,2,12,48) + TEST_ITEM_INPLACE(0,>>,1,24,12) + l[4] = 15; + TEST_ITEM_INPLACE(4,&,(16+4+1),5,5) + TEST_ITEM_INPLACE(0,^,1,13,12) + TEST_ITEM_INPLACE(0,|,1,13,13) + + o.attr("x0") = 0; + o.attr("x1") = 1; + o.attr("x2") = 2; + o.attr("x3") = 0; + o.attr("x4") = 1; + +#define TEST_ATTR_INPLACE(index, op, n, r1, r2) \ + o.attr("x" #index) op##= n; \ + if (o.attr("x" #index) != r1) \ + return false; \ + o.attr("x" #index) op##= object(n); \ + if (o.attr("x" #index) != r2) \ + return false; + + TEST_ATTR_INPLACE(0,+,7,7,14) + TEST_ATTR_INPLACE(1,-,2,-1,-3) + TEST_ATTR_INPLACE(2,*,3,6,18) + TEST_ATTR_INPLACE(2,/,2,9,4) + TEST_ATTR_INPLACE(0,%,4,2,2) + o.attr("x0") += 1; + TEST_ATTR_INPLACE(0,<<,2,12,48) + TEST_ATTR_INPLACE(0,>>,1,24,12) + o.attr("x4") = 15; + TEST_ATTR_INPLACE(4,&,(16+4+1),5,5) + TEST_ATTR_INPLACE(0,^,1,13,12) + TEST_ATTR_INPLACE(0,|,1,13,13) + + if (l[0] != o.attr("x0")) + return false; + if (l[1] != o.attr("x1")) + return false; + if (l[2] != o.attr("x2")) + return false; + if (l[3] != o.attr("x3")) + return false; + if (l[4] != o.attr("x4")) + return false; + + return true; +} + BOOST_PYTHON_MODULE_INIT(object_ext) { module("object_ext") .def("call_object_3", call_object_3) .def("message", message) .def("number", number) - + .def("obj_getattr", obj_getattr) .def("obj_const_getattr", obj_const_getattr) .def("obj_setattr", obj_setattr) .def("obj_setattr42", obj_setattr42) .def("obj_moveattr", obj_moveattr) - + .def("obj_getitem", obj_getitem) .def("obj_getitem3", obj_getitem) .def("obj_const_getitem", obj_const_getitem) @@ -134,7 +275,7 @@ BOOST_PYTHON_MODULE_INIT(object_ext) .def("obj_setitem42", obj_setitem42) .def("obj_moveitem", obj_moveitem) .def("obj_moveitem2", obj_moveitem2) - + .def("test", test) .def("test_not", test_not) @@ -143,6 +284,9 @@ BOOST_PYTHON_MODULE_INIT(object_ext) .def("test_item", test_item) .def("test_not_item", test_not_item) + + .def("check_binary_operators", check_binary_operators) + .def("check_inplace", check_inplace) ; } diff --git a/test/object.py b/test/object.py index 68e46fe3..e3cb9dc1 100644 --- a/test/object.py +++ b/test/object.py @@ -79,6 +79,14 @@ 0 >>> test_not_item(d, 'foo') 1 + + Operators + + +>>> assert check_binary_operators() +>>> class X: pass +... +>>> assert check_inplace(range(3), X()) ''' def run(args = None):