From 3b969bf94c2e562c07e3e7c118d43dd82627cf4c Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Tue, 16 Jan 2024 15:04:09 -0500 Subject: [PATCH] Squashed 'wrap/' changes from bd57210d9..cdcf23207 cdcf23207 Merge pull request #163 from borglab/dunder-methods 22d429cc2 use STL functions instead of container methods abe8818ab update pyparsing to 3.1.1 a9e896d0c add unit tests 535fe4f14 wrap supported dunder methods 079687eac parse dunder methods in interface file git-subtree-dir: wrap git-subtree-split: cdcf23207fbb03457c5d9dbfc2b0b57e515b5f3d --- gtwrap/interface_parser/classes.py | 48 +++++++++++--- gtwrap/interface_parser/function.py | 2 +- gtwrap/interface_parser/tokens.py | 1 + gtwrap/pybind_wrapper.py | 64 +++++++++++++++++++ gtwrap/template_instantiator/classes.py | 3 + requirements.txt | 2 +- tests/expected/matlab/FastSet.m | 36 +++++++++++ tests/expected/matlab/MyFactorPosePoint2.m | 12 ++-- tests/expected/matlab/class_wrapper.cpp | 74 ++++++++++++++++++---- tests/expected/python/class_pybind.cpp | 6 ++ tests/fixtures/class.i | 9 +++ tests/test_interface_parser.py | 22 +++++-- 12 files changed, 245 insertions(+), 34 deletions(-) create mode 100644 tests/expected/matlab/FastSet.m diff --git a/gtwrap/interface_parser/classes.py b/gtwrap/interface_parser/classes.py index b63a0b5eb..8967bea93 100644 --- a/gtwrap/interface_parser/classes.py +++ b/gtwrap/interface_parser/classes.py @@ -12,13 +12,14 @@ Author: Duy Nguyen Ta, Fan Jiang, Matthew Sklar, Varun Agrawal, and Frank Dellae from typing import Any, Iterable, List, Union -from pyparsing import Literal, Optional, ZeroOrMore # type: ignore +from pyparsing import ZeroOrMore # type: ignore +from pyparsing import Literal, Optional, Word, alphas from .enum import Enum from .function import ArgumentList, ReturnType from .template import Template -from .tokens import (CLASS, COLON, CONST, IDENT, LBRACE, LPAREN, OPERATOR, - RBRACE, RPAREN, SEMI_COLON, STATIC, VIRTUAL) +from .tokens import (CLASS, COLON, CONST, DUNDER, IDENT, LBRACE, LPAREN, + OPERATOR, RBRACE, RPAREN, SEMI_COLON, STATIC, VIRTUAL) from .type import TemplatedType, Typename from .utils import collect_namespaces from .variable import Variable @@ -212,6 +213,26 @@ class Operator: ) +class DunderMethod: + """Special Python double-underscore (dunder) methods, e.g. __iter__, __contains__""" + rule = ( + DUNDER # + + (Word(alphas))("name") # + + DUNDER # + + LPAREN # + + ArgumentList.rule("args_list") # + + RPAREN # + + SEMI_COLON # BR + ).setParseAction(lambda t: DunderMethod(t.name, t.args_list)) + + def __init__(self, name: str, args: ArgumentList): + self.name = name + self.args = args + + def __repr__(self) -> str: + return f"DunderMethod: __{self.name}__({self.args})" + + class Class: """ Rule to parse a class defined in the interface file. @@ -223,11 +244,13 @@ class Class: }; ``` """ + class Members: """ Rule for all the members within a class. """ - rule = ZeroOrMore(Constructor.rule # + rule = ZeroOrMore(DunderMethod.rule # + ^ Constructor.rule # ^ Method.rule # ^ StaticMethod.rule # ^ Variable.rule # @@ -235,11 +258,12 @@ class Class: ^ Enum.rule # ).setParseAction(lambda t: Class.Members(t.asList())) - def __init__(self, - members: List[Union[Constructor, Method, StaticMethod, - Variable, Operator]]): + def __init__(self, members: List[Union[Constructor, Method, + StaticMethod, Variable, + Operator, Enum, DunderMethod]]): self.ctors = [] self.methods = [] + self.dunder_methods = [] self.static_methods = [] self.properties = [] self.operators = [] @@ -251,6 +275,8 @@ class Class: self.methods.append(m) elif isinstance(m, StaticMethod): self.static_methods.append(m) + elif isinstance(m, DunderMethod): + self.dunder_methods.append(m) elif isinstance(m, Variable): self.properties.append(m) elif isinstance(m, Operator): @@ -271,8 +297,8 @@ class Class: + SEMI_COLON # BR ).setParseAction(lambda t: Class( t.template, t.is_virtual, t.name, t.parent_class, t.members.ctors, t. - members.methods, t.members.static_methods, t.members.properties, t. - members.operators, t.members.enums)) + members.methods, t.members.static_methods, t.members.dunder_methods, t. + members.properties, t.members.operators, t.members.enums)) def __init__( self, @@ -283,6 +309,7 @@ class Class: ctors: List[Constructor], methods: List[Method], static_methods: List[StaticMethod], + dunder_methods: List[DunderMethod], properties: List[Variable], operators: List[Operator], enums: List[Enum], @@ -308,6 +335,7 @@ class Class: self.ctors = ctors self.methods = methods self.static_methods = static_methods + self.dunder_methods = dunder_methods self.properties = properties self.operators = operators self.enums = enums @@ -326,6 +354,8 @@ class Class: method.parent = self for static_method in self.static_methods: static_method.parent = self + for dunder_method in self.dunder_methods: + dunder_method.parent = self for _property in self.properties: _property.parent = self diff --git a/gtwrap/interface_parser/function.py b/gtwrap/interface_parser/function.py index b40884488..5385c744f 100644 --- a/gtwrap/interface_parser/function.py +++ b/gtwrap/interface_parser/function.py @@ -82,7 +82,7 @@ class ArgumentList: return ArgumentList([]) def __repr__(self) -> str: - return repr(tuple(self.args_list)) + return ",".join([repr(x) for x in self.args_list]) def __len__(self) -> int: return len(self.args_list) diff --git a/gtwrap/interface_parser/tokens.py b/gtwrap/interface_parser/tokens.py index 02e6d82f8..11c99d19c 100644 --- a/gtwrap/interface_parser/tokens.py +++ b/gtwrap/interface_parser/tokens.py @@ -22,6 +22,7 @@ RAW_POINTER, SHARED_POINTER, REF = map(Literal, "@*&") LPAREN, RPAREN, LBRACE, RBRACE, COLON, SEMI_COLON = map(Suppress, "(){}:;") LOPBRACK, ROPBRACK, COMMA, EQUAL = map(Suppress, "<>,=") +DUNDER = Suppress(Literal("__")) # Default argument passed to functions/methods. # Allow anything up to ',' or ';' except when they diff --git a/gtwrap/pybind_wrapper.py b/gtwrap/pybind_wrapper.py index 78730a909..479c2d67d 100755 --- a/gtwrap/pybind_wrapper.py +++ b/gtwrap/pybind_wrapper.py @@ -45,6 +45,8 @@ class PybindWrapper: 'continue', 'global', 'pass' ] + self.dunder_methods = ('len', 'contains', 'iter') + # amount of indentation to add before each function/method declaration. self.method_indent = '\n' + (' ' * 8) @@ -153,6 +155,51 @@ class PybindWrapper: suffix=suffix) return ret + def _wrap_dunder(self, + method, + cpp_class, + prefix, + suffix, + method_suffix=""): + """ + Wrap a Python double-underscore (dunder) method. + + E.g. __len__() gets wrapped as `.def("__len__", [](gtsam::KeySet* self) {return self->size();})` + + Supported methods are: + - __contains__(T x) + - __len__() + - __iter__() + """ + py_method = method.name + method_suffix + args_names = method.args.names() + py_args_names = self._py_args_names(method.args) + args_signature_with_names = self._method_args_signature(method.args) + + if method.name == 'len': + function_call = "return std::distance(self->begin(), self->end());" + elif method.name == 'contains': + function_call = f"return std::find(self->begin(), self->end(), {method.args.args_list[0].name}) != self->end();" + elif method.name == 'iter': + function_call = "return py::make_iterator(self->begin(), self->end());" + + ret = ('{prefix}.def("__{py_method}__",' + '[]({self}{opt_comma}{args_signature_with_names}){{' + '{function_call}' + '}}' + '{py_args_names}){suffix}'.format( + prefix=prefix, + py_method=py_method, + self=f"{cpp_class}* self", + opt_comma=', ' if args_names else '', + args_signature_with_names=args_signature_with_names, + function_call=function_call, + py_args_names=py_args_names, + suffix=suffix, + )) + + return ret + def _wrap_method(self, method, cpp_class, @@ -235,6 +282,20 @@ class PybindWrapper: return ret + def wrap_dunder_methods(self, + methods, + cpp_class, + prefix='\n' + ' ' * 8, + suffix=''): + res = "" + for method in methods: + res += self._wrap_dunder(method=method, + cpp_class=cpp_class, + prefix=prefix, + suffix=suffix) + + return res + def wrap_methods(self, methods, cpp_class, @@ -398,6 +459,7 @@ class PybindWrapper: '{wrapped_ctors}' '{wrapped_methods}' '{wrapped_static_methods}' + '{wrapped_dunder_methods}' '{wrapped_properties}' '{wrapped_operators};\n'.format( class_declaration=class_declaration, @@ -406,6 +468,8 @@ class PybindWrapper: instantiated_class.methods, cpp_class), wrapped_static_methods=self.wrap_methods( instantiated_class.static_methods, cpp_class), + wrapped_dunder_methods=self.wrap_dunder_methods( + instantiated_class.dunder_methods, cpp_class), wrapped_properties=self.wrap_properties( instantiated_class.properties, cpp_class), wrapped_operators=self.wrap_operators( diff --git a/gtwrap/template_instantiator/classes.py b/gtwrap/template_instantiator/classes.py index ce51d5b96..702654678 100644 --- a/gtwrap/template_instantiator/classes.py +++ b/gtwrap/template_instantiator/classes.py @@ -57,6 +57,8 @@ class InstantiatedClass(parser.Class): # Instantiate all instance methods self.methods = self.instantiate_methods(typenames) + + self.dunder_methods = original.dunder_methods super().__init__( self.template, @@ -66,6 +68,7 @@ class InstantiatedClass(parser.Class): self.ctors, self.methods, self.static_methods, + self.dunder_methods, self.properties, self.operators, self.enums, diff --git a/requirements.txt b/requirements.txt index 0aac9302e..f43fdda61 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ -pyparsing==2.4.7 +pyparsing==3.1.1 pytest>=6.2.4 diff --git a/tests/expected/matlab/FastSet.m b/tests/expected/matlab/FastSet.m new file mode 100644 index 000000000..4d2a1813e --- /dev/null +++ b/tests/expected/matlab/FastSet.m @@ -0,0 +1,36 @@ +%class FastSet, see Doxygen page for details +%at https://gtsam.org/doxygen/ +% +%-------Constructors------- +%FastSet() +% +classdef FastSet < handle + properties + ptr_FastSet = 0 + end + methods + function obj = FastSet(varargin) + if nargin == 2 && isa(varargin{1}, 'uint64') && varargin{1} == uint64(5139824614673773682) + my_ptr = varargin{2}; + class_wrapper(73, my_ptr); + elseif nargin == 0 + my_ptr = class_wrapper(74); + else + error('Arguments do not match any overload of FastSet constructor'); + end + obj.ptr_FastSet = my_ptr; + end + + function delete(obj) + class_wrapper(75, obj.ptr_FastSet); + end + + function display(obj), obj.print(''); end + %DISPLAY Calls print on the object + function disp(obj), obj.display; end + %DISP Calls print on the object + end + + methods(Static = true) + end +end diff --git a/tests/expected/matlab/MyFactorPosePoint2.m b/tests/expected/matlab/MyFactorPosePoint2.m index 4a30bd489..ac5b134f9 100644 --- a/tests/expected/matlab/MyFactorPosePoint2.m +++ b/tests/expected/matlab/MyFactorPosePoint2.m @@ -15,9 +15,9 @@ classdef MyFactorPosePoint2 < handle function obj = MyFactorPosePoint2(varargin) if nargin == 2 && isa(varargin{1}, 'uint64') && varargin{1} == uint64(5139824614673773682) my_ptr = varargin{2}; - class_wrapper(73, my_ptr); + class_wrapper(76, my_ptr); elseif nargin == 4 && isa(varargin{1},'numeric') && isa(varargin{2},'numeric') && isa(varargin{3},'double') && isa(varargin{4},'gtsam.noiseModel.Base') - my_ptr = class_wrapper(74, varargin{1}, varargin{2}, varargin{3}, varargin{4}); + my_ptr = class_wrapper(77, varargin{1}, varargin{2}, varargin{3}, varargin{4}); else error('Arguments do not match any overload of MyFactorPosePoint2 constructor'); end @@ -25,7 +25,7 @@ classdef MyFactorPosePoint2 < handle end function delete(obj) - class_wrapper(75, obj.ptr_MyFactorPosePoint2); + class_wrapper(78, obj.ptr_MyFactorPosePoint2); end function display(obj), obj.print(''); end @@ -36,19 +36,19 @@ classdef MyFactorPosePoint2 < handle % PRINT usage: print(string s, KeyFormatter keyFormatter) : returns void % Doxygen can be found at https://gtsam.org/doxygen/ if length(varargin) == 2 && isa(varargin{1},'char') && isa(varargin{2},'gtsam.KeyFormatter') - class_wrapper(76, this, varargin{:}); + class_wrapper(79, this, varargin{:}); return end % PRINT usage: print(string s) : returns void % Doxygen can be found at https://gtsam.org/doxygen/ if length(varargin) == 1 && isa(varargin{1},'char') - class_wrapper(77, this, varargin{:}); + class_wrapper(80, this, varargin{:}); return end % PRINT usage: print() : returns void % Doxygen can be found at https://gtsam.org/doxygen/ if length(varargin) == 0 - class_wrapper(78, this, varargin{:}); + class_wrapper(81, this, varargin{:}); return end error('Arguments do not match any overload of function MyFactorPosePoint2.print'); diff --git a/tests/expected/matlab/class_wrapper.cpp b/tests/expected/matlab/class_wrapper.cpp index c4be52018..e33f14238 100644 --- a/tests/expected/matlab/class_wrapper.cpp +++ b/tests/expected/matlab/class_wrapper.cpp @@ -31,6 +31,8 @@ typedef std::set*> Collector_ForwardKinematic static Collector_ForwardKinematics collector_ForwardKinematics; typedef std::set*> Collector_TemplatedConstructor; static Collector_TemplatedConstructor collector_TemplatedConstructor; +typedef std::set*> Collector_FastSet; +static Collector_FastSet collector_FastSet; typedef std::set*> Collector_MyFactorPosePoint2; static Collector_MyFactorPosePoint2 collector_MyFactorPosePoint2; @@ -101,6 +103,12 @@ void _deleteAllObjects() collector_TemplatedConstructor.erase(iter++); anyDeleted = true; } } + { for(Collector_FastSet::iterator iter = collector_FastSet.begin(); + iter != collector_FastSet.end(); ) { + delete *iter; + collector_FastSet.erase(iter++); + anyDeleted = true; + } } { for(Collector_MyFactorPosePoint2::iterator iter = collector_MyFactorPosePoint2.begin(); iter != collector_MyFactorPosePoint2.end(); ) { delete *iter; @@ -844,7 +852,40 @@ void TemplatedConstructor_deconstructor_72(int nargout, mxArray *out[], int narg delete self; } -void MyFactorPosePoint2_collectorInsertAndMakeBase_73(int nargout, mxArray *out[], int nargin, const mxArray *in[]) +void FastSet_collectorInsertAndMakeBase_73(int nargout, mxArray *out[], int nargin, const mxArray *in[]) +{ + mexAtExit(&_deleteAllObjects); + typedef std::shared_ptr Shared; + + Shared *self = *reinterpret_cast (mxGetData(in[0])); + collector_FastSet.insert(self); +} + +void FastSet_constructor_74(int nargout, mxArray *out[], int nargin, const mxArray *in[]) +{ + mexAtExit(&_deleteAllObjects); + typedef std::shared_ptr Shared; + + Shared *self = new Shared(new FastSet()); + collector_FastSet.insert(self); + out[0] = mxCreateNumericMatrix(1, 1, mxUINT32OR64_CLASS, mxREAL); + *reinterpret_cast (mxGetData(out[0])) = self; +} + +void FastSet_deconstructor_75(int nargout, mxArray *out[], int nargin, const mxArray *in[]) +{ + typedef std::shared_ptr Shared; + checkArguments("delete_FastSet",nargout,nargin,1); + Shared *self = *reinterpret_cast(mxGetData(in[0])); + Collector_FastSet::iterator item; + item = collector_FastSet.find(self); + if(item != collector_FastSet.end()) { + collector_FastSet.erase(item); + } + delete self; +} + +void MyFactorPosePoint2_collectorInsertAndMakeBase_76(int nargout, mxArray *out[], int nargin, const mxArray *in[]) { mexAtExit(&_deleteAllObjects); typedef std::shared_ptr> Shared; @@ -853,7 +894,7 @@ void MyFactorPosePoint2_collectorInsertAndMakeBase_73(int nargout, mxArray *out[ collector_MyFactorPosePoint2.insert(self); } -void MyFactorPosePoint2_constructor_74(int nargout, mxArray *out[], int nargin, const mxArray *in[]) +void MyFactorPosePoint2_constructor_77(int nargout, mxArray *out[], int nargin, const mxArray *in[]) { mexAtExit(&_deleteAllObjects); typedef std::shared_ptr> Shared; @@ -868,7 +909,7 @@ void MyFactorPosePoint2_constructor_74(int nargout, mxArray *out[], int nargin, *reinterpret_cast (mxGetData(out[0])) = self; } -void MyFactorPosePoint2_deconstructor_75(int nargout, mxArray *out[], int nargin, const mxArray *in[]) +void MyFactorPosePoint2_deconstructor_78(int nargout, mxArray *out[], int nargin, const mxArray *in[]) { typedef std::shared_ptr> Shared; checkArguments("delete_MyFactorPosePoint2",nargout,nargin,1); @@ -881,7 +922,7 @@ void MyFactorPosePoint2_deconstructor_75(int nargout, mxArray *out[], int nargin delete self; } -void MyFactorPosePoint2_print_76(int nargout, mxArray *out[], int nargin, const mxArray *in[]) +void MyFactorPosePoint2_print_79(int nargout, mxArray *out[], int nargin, const mxArray *in[]) { checkArguments("print",nargout,nargin-1,2); auto obj = unwrap_shared_ptr>(in[0], "ptr_MyFactorPosePoint2"); @@ -890,7 +931,7 @@ void MyFactorPosePoint2_print_76(int nargout, mxArray *out[], int nargin, const obj->print(s,keyFormatter); } -void MyFactorPosePoint2_print_77(int nargout, mxArray *out[], int nargin, const mxArray *in[]) +void MyFactorPosePoint2_print_80(int nargout, mxArray *out[], int nargin, const mxArray *in[]) { checkArguments("print",nargout,nargin-1,1); auto obj = unwrap_shared_ptr>(in[0], "ptr_MyFactorPosePoint2"); @@ -898,7 +939,7 @@ void MyFactorPosePoint2_print_77(int nargout, mxArray *out[], int nargin, const obj->print(s,gtsam::DefaultKeyFormatter); } -void MyFactorPosePoint2_print_78(int nargout, mxArray *out[], int nargin, const mxArray *in[]) +void MyFactorPosePoint2_print_81(int nargout, mxArray *out[], int nargin, const mxArray *in[]) { checkArguments("print",nargout,nargin-1,0); auto obj = unwrap_shared_ptr>(in[0], "ptr_MyFactorPosePoint2"); @@ -1137,22 +1178,31 @@ void mexFunction(int nargout, mxArray *out[], int nargin, const mxArray *in[]) TemplatedConstructor_deconstructor_72(nargout, out, nargin-1, in+1); break; case 73: - MyFactorPosePoint2_collectorInsertAndMakeBase_73(nargout, out, nargin-1, in+1); + FastSet_collectorInsertAndMakeBase_73(nargout, out, nargin-1, in+1); break; case 74: - MyFactorPosePoint2_constructor_74(nargout, out, nargin-1, in+1); + FastSet_constructor_74(nargout, out, nargin-1, in+1); break; case 75: - MyFactorPosePoint2_deconstructor_75(nargout, out, nargin-1, in+1); + FastSet_deconstructor_75(nargout, out, nargin-1, in+1); break; case 76: - MyFactorPosePoint2_print_76(nargout, out, nargin-1, in+1); + MyFactorPosePoint2_collectorInsertAndMakeBase_76(nargout, out, nargin-1, in+1); break; case 77: - MyFactorPosePoint2_print_77(nargout, out, nargin-1, in+1); + MyFactorPosePoint2_constructor_77(nargout, out, nargin-1, in+1); break; case 78: - MyFactorPosePoint2_print_78(nargout, out, nargin-1, in+1); + MyFactorPosePoint2_deconstructor_78(nargout, out, nargin-1, in+1); + break; + case 79: + MyFactorPosePoint2_print_79(nargout, out, nargin-1, in+1); + break; + case 80: + MyFactorPosePoint2_print_80(nargout, out, nargin-1, in+1); + break; + case 81: + MyFactorPosePoint2_print_81(nargout, out, nargin-1, in+1); break; } } catch(const std::exception& e) { diff --git a/tests/expected/python/class_pybind.cpp b/tests/expected/python/class_pybind.cpp index 86d69c2e0..2292f46be 100644 --- a/tests/expected/python/class_pybind.cpp +++ b/tests/expected/python/class_pybind.cpp @@ -91,6 +91,12 @@ PYBIND11_MODULE(class_py, m_) { .def(py::init(), py::arg("arg")) .def(py::init(), py::arg("arg")); + py::class_>(m_, "FastSet") + .def(py::init<>()) + .def("__len__",[](FastSet* self){return std::distance(self->begin(), self->end());}) + .def("__contains__",[](FastSet* self, size_t key){return std::find(self->begin(), self->end(), key) != self->end();}, py::arg("key")) + .def("__iter__",[](FastSet* self){return py::make_iterator(self->begin(), self->end());}); + py::class_, std::shared_ptr>>(m_, "MyFactorPosePoint2") .def(py::init>(), py::arg("key1"), py::arg("key2"), py::arg("measured"), py::arg("noiseModel")) .def("print",[](MyFactor* self, const string& s, const gtsam::KeyFormatter& keyFormatter){ py::scoped_ostream_redirect output; self->print(s, keyFormatter);}, py::arg("s") = "factor: ", py::arg("keyFormatter") = gtsam::DefaultKeyFormatter) diff --git a/tests/fixtures/class.i b/tests/fixtures/class.i index 766f55329..775bbc737 100644 --- a/tests/fixtures/class.i +++ b/tests/fixtures/class.i @@ -145,3 +145,12 @@ class TemplatedConstructor { class SuperCoolFactor; typedef SuperCoolFactor SuperCoolFactorPose3; + +/// @brief class with dunder methods for container behavior +class FastSet { + FastSet(); + + __len__(); + __contains__(size_t key); + __iter__(); +}; \ No newline at end of file diff --git a/tests/test_interface_parser.py b/tests/test_interface_parser.py index 45415995f..2a923b3c5 100644 --- a/tests/test_interface_parser.py +++ b/tests/test_interface_parser.py @@ -18,11 +18,12 @@ import unittest sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) -from gtwrap.interface_parser import (ArgumentList, Class, Constructor, Enum, - Enumerator, ForwardDeclaration, - GlobalFunction, Include, Method, Module, - Namespace, Operator, ReturnType, - StaticMethod, TemplatedType, Type, +from gtwrap.interface_parser import (ArgumentList, Class, Constructor, + DunderMethod, Enum, Enumerator, + ForwardDeclaration, GlobalFunction, + Include, Method, Module, Namespace, + Operator, ReturnType, StaticMethod, + TemplatedType, Type, TypedefTemplateInstantiation, Typename, Variable) from gtwrap.template_instantiator.classes import InstantiatedClass @@ -344,6 +345,17 @@ class TestInterfaceParser(unittest.TestCase): self.assertEqual(1, len(ret.args)) self.assertEqual("const T & name", ret.args.args_list[0].to_cpp()) + def test_dunder_method(self): + """Test for special python dunder methods.""" + iter_string = "__iter__();" + ret = DunderMethod.rule.parse_string(iter_string)[0] + self.assertEqual("iter", ret.name) + + contains_string = "__contains__(size_t key);" + ret = DunderMethod.rule.parse_string(contains_string)[0] + self.assertEqual("contains", ret.name) + self.assertTrue(len(ret.args) == 1) + def test_operator_overload(self): """Test for operator overloading.""" # Unary operator