I am wrapping (many) c++ classes with boost::python. If I mistype the attribute name when assigning it from a python script, python silently creates new instance attribute, which is never what I indent. Is there a way to catch such events (and raise exception?)?
I've seen a few posts on the web on the topic, but none of them seemd to give a definitive answer; I was trying to override __setattr__
& friends, but I was not able to get it right; plus I was concerned about possible performance impact.
Cheers, Vaclav
__setattr__
is in fact the way to go. If you're worried about performance, I think python still does this call internally anyway, so overriding the function should not cost any more. The problem with __setattr__
is that if you're trying to keep your implementation inside your c++ code, it can be tricky to implement.
Here's a python version:
# MyClassBase defined in C++ library
class MyClass(object):
def __init__(self):
self.test1 = 'test1'
self.__initialized = True
def __setattr__(self, name, value):
if not self.__dict__.has_key('_MyClass__initialized') or self.__dict__.has_key(name):
object.__setattr__(self, name, value)
else:
raise AttributeError("Attribute %s does not exist." % name)
def main():
o = MyClass()
print o.test1
o.test1 = 'test1_set'
print o.test1
# This will throw
o.test2 = 'test2_set'
if __name__ == '__main__':
main()
Here's a way to do it using metaclasses. The advantage of this method is that you only have to define the __setattr__
function once, then you can just define each class using your injector class:
# create an injector metaclass to add functionality to
# our modules, you can reuse BoostPythonMetaclass for
# all boost::python-exported objects
BoostPythonMetaclass = MyClass.__class__
class injector(object):
class __metaclass__(BoostPythonMetaclass):
def __init__(self, name, bases, dict):
for b in bases:
if type(b) not in (self, type):
for k,v in dict.items():
setattr(b,k,v)
setattr(b, '__initialized', True)
return type.__init__(self, name, bases, dict)
def __setattr__(self, name, value):
if not self.__dict__.has_key('_MyClass__initialized') or self.__dict__.has_key(name):
object.__setattr__(self, name, value)
else:
raise AttributeError("Attribute %s does not exist." % name)
# Use the injector to add our functionality
class MyClass(injector, MyClass):
pass
If you want to do the same in c++, it's a bit trickier:
using namespace boost::python;
static void SetAttr(object self, str name, object value)
{
dict d = getattr(self, "__dict__");
if(d.has_key(name)) {
setattr(self, name, value);
} else {
std::stringstream ss;
ss << "Method" << extract<std::string>(name) << "does not exist.";
PyErr_SetString(PyExc_AttributeError, ss.str().c_str());
throw error_already_set();
}
}
BOOST_PYTHON_MODULE(mymodule)
{
class_<MyClass>("MyClass")
.def("__setattr__", &SetAttr);
}
using namespace boost::python;
static object __setattr__native; //#Object for saving native boost __setattr__() method
//#Hook-setter for method __setattr__(). Checks attribute exists and call native __setattr__ if its exists
static void SetAttr_only_exists(object self, str name, object value)
{
getattr(self, name); //#Get attribute or exception if attribute not exists
__setattr__native(self, name, value); //#Call native __setattr__()
return;
}
BOOST_PYTHON_MODULE(mymodule)
{
myClass = class_<MyClass>("MyClass")
.def("someMethod", &MyClass::someMethod);
__setattr__native = myClass.attr("__setattr__"); //#Saving boost __setattr__() method
myClass.def("__setattr__", &SetAttr_only_exists); //#Set __setattr__() hook method
}
来源:https://stackoverflow.com/questions/6120490/catch-creation-of-instance-attributes-of-boostpython-wrapped-classes-from-c