Disallow adding attributes to wrapped classes from Python
Is there a way to make the __dict__ of exposed classes read-only? I fear it will be a common error for Python programmers to make typos when setting attributes, and I would like this to generate an error rather than silently adding a new attribute. class_<Foo>("Foo") .add_property("foobar", &Foo::getFooBar, &Foo::setFooBar) ;
foo.foobaz = 7 # oops: typo! foo.foobaz 7
-- Stefan Haller Ableton http://www.ableton.com/
I think it should be possible to catch the __setattr__() of __dict__ and to throw an exception. However, I think that misses out the philosophy of Python. Variables are not declared. It is up to the user to define appropriate test cases that check the correctness of the code. You could also define a function that sets the value of foobar. If the user writes foo.set_foobaz(7) he/she will get an exception. Andreas Stefan Haller wrote:
Is there a way to make the __dict__ of exposed classes read-only? I fear it will be a common error for Python programmers to make typos when setting attributes, and I would like this to generate an error rather than silently adding a new attribute.
class_<Foo>("Foo") .add_property("foobar", &Foo::getFooBar, &Foo::setFooBar) ;
foo.foobaz = 7 # oops: typo! foo.foobaz
7
-- -------------------------------------- Dr. Andreas Beyer - beyer@imb-jena.de http://www.imb-jena.de/~beyer --------------------------------------
Stefan Haller wrote:
Is there a way to make the __dict__ of exposed classes read-only? I fear it will be a common error for Python programmers to make typos when setting attributes, and I would like this to generate an error rather than silently adding a new attribute.
class_<Foo>("Foo") .add_property("foobar", &Foo::getFooBar, &Foo::setFooBar) ;
foo.foobaz = 7 # oops: typo! foo.foobaz 7
Just the same way you do it in Python: $ python Python 2.3.2 (#1, Nov 14 2003, 18:13:01) [GCC 3.3.1 (cygming special)] on cygwin Type "help", "copyright", "credits" or "license" for more information.
class X(object): ... __slots__ = ('a', 'b') ... def foo(self): ... pass ... x = X() x.a = 1 x.b = 2 x.c = 3 Traceback (most recent call last): File "<stdin>", line 1, in ? AttributeError: 'X' object has no attribute 'c'
Thus: class_<Foo>("Foo") .add_property("foobar", &Foo::getFooBar, &Foo::setFooBar) .attr("__slots__") = tuple() ; HTH, -- Dave Abrahams Boost Consulting http://www.boost-consulting.com
David Abrahams <dave@boost-consulting.com> wrote:
class_<Foo>("Foo") .add_property("foobar", &Foo::getFooBar, &Foo::setFooBar) .attr("__slots__") = tuple() ;
Thanks for the quick help, but unfortunately it doesn't work. I suspect this may be for the same reason why the following doesn't work: $ python Python 2.2.3 (#1, Jan 21 2004, 06:55:19) [GCC 3.3 20030304 (Apple Computer, Inc. build 1495)] on darwin Type "help", "copyright", "credits" or "license" for more information.
class X(object): ... pass ... X.__slots__ = () x = X() x.foobar = 1 x.foobar 1
Or does it make a difference that I'm still using Python 2.2? -- Stefan Haller Ableton http://www.ableton.com/
Stefan Haller wrote:
David Abrahams <dave@boost-consulting.com> wrote:
class_<Foo>("Foo") .add_property("foobar", &Foo::getFooBar, &Foo::setFooBar) .attr("__slots__") = tuple() ;
Thanks for the quick help, but unfortunately it doesn't work. I suspect this may be for the same reason why the following doesn't work:
$ python Python 2.2.3 (#1, Jan 21 2004, 06:55:19) [GCC 3.3 20030304 (Apple Computer, Inc. build 1495)] on darwin Type "help", "copyright", "credits" or "license" for more information.
class X(object): ... pass ... X.__slots__ = () x = X() x.foobar = 1 x.foobar 1
Or does it make a difference that I'm still using Python 2.2?
Naw, you're right. Are you willing to use the Boost CVS? I'd be happy to add a patch to support this: dict d; d["__slots__"] = tuple(); class_<Foo>( "Foo", // name tuple(), // more bases d) ... You could fake it, but it wouldn't work nearly as well: object proxy = class<anything>("anything") .add_property("foobar", &Foo::getFooBar, &Foo::setFooBar); object metaclass = proxy.attr("__class__"); dict d = proxy().attr("__dict__"); d["__slots__"] = tuple(); scope().attr("Foo") = metaclass("Foo", tuple(), d); You'd lose the benefit of a conversion from C++ Foo to Python, for example. But you might try the above anyway just to make sure I'm not wasting my time with the patch. -- Dave Abrahams Boost Consulting http://www.boost-consulting.com
David Abrahams <dave@boost-consulting.com> wrote:
Naw, you're right. Are you willing to use the Boost CVS?
Not easily, sorry. And I'm not sure if the feature is important enough for us to go to the trouble, and to waste your time on it. (But I could easily use a patch if it can be applied against the 1.32.0 release; I have no idea how realistic this is.)
You could fake it, but it wouldn't work nearly as well:
object proxy = class<anything>("anything") .add_property("foobar", &Foo::getFooBar, &Foo::setFooBar);
object metaclass = proxy.attr("__class__");
dict d = proxy().attr("__dict__"); d["__slots__"] = tuple(); scope().attr("Foo") = metaclass("Foo", tuple(), d);
You'd lose the benefit of a conversion from C++ Foo to Python, for example. But you might try the above anyway just to make sure I'm not wasting my time with the patch.
Phew. I'm afraid I don't understand this code at all. I tried to get it to work for a simple test case, with the following adjustments to make it compile (not really knowing what I was doing): - = class<anything>("anything") + = class_<Foo>("anything") - dict d = proxy().attr("__dict__"); + dict d(proxy().attr("__dict__")); But no, this doesn't work. It *does* prevent me from adding new attributes, but it also doesn't show the existing property (foobar), which is not much progress. :-)
f = Foo() f.foobar Traceback (most recent call last): File "<string>", line 1, in ? AttributeError: 'Foo' object has no attribute 'foobar' f.x = 1 Traceback (most recent call last): File "<string>", line 1, in ? AttributeError: 'Foo' object has no attribute 'x' dir(f) ['__class__', '__delattr__', '__doc__', '__getattribute__', '__getstate__', '__hash__', '__init__', '__new__', '__reduce__', '__repr__', '__setattr__', '__slots__', '__str__']
-- Stefan Haller Ableton http://www.ableton.com/
Stefan Haller wrote:
David Abrahams <dave@boost-consulting.com> wrote:
Naw, you're right. Are you willing to use the Boost CVS?
Not easily, sorry. And I'm not sure if the feature is important enough for us to go to the trouble, and to waste your time on it.
(But I could easily use a patch if it can be applied against the 1.32.0 release; I have no idea how realistic this is.)
Nearly as easy as making the change in CVS. What I'd like from you first is a test case along the lines of any one in libs/python/test (one xxxx.cpp and one xxxx.py) that demonstrates the problem you're trying to solve and fails unless it is solved. Then I can make the changes to the codebase and update the docs.
You could fake it, but it wouldn't work nearly as well:
object proxy = class<anything>("anything") .add_property("foobar", &Foo::getFooBar, &Foo::setFooBar);
object metaclass = proxy.attr("__class__");
dict d = proxy().attr("__dict__"); d["__slots__"] = tuple(); scope().attr("Foo") = metaclass("Foo", tuple(), d);
You'd lose the benefit of a conversion from C++ Foo to Python, for example. But you might try the above anyway just to make sure I'm not wasting my time with the patch.
Phew. I'm afraid I don't understand this code at all. I tried to get it to work for a simple test case, with the following adjustments to make it compile (not really knowing what I was doing):
- = class<anything>("anything") + = class_<Foo>("anything")
No, I really did mean for you to wrap some other class here. Just declare struct anything{};
- dict d = proxy().attr("__dict__"); + dict d(proxy().attr("__dict__"));
But no, this doesn't work. It *does* prevent me from adding new attributes, but it also doesn't show the existing property (foobar), which is not much progress. :-)
Oh, heck. I know why.
f = Foo() f.foobar Traceback (most recent call last): File "<string>", line 1, in ? AttributeError: 'Foo' object has no attribute 'foobar' f.x = 1 Traceback (most recent call last): File "<string>", line 1, in ? AttributeError: 'Foo' object has no attribute 'x' dir(f) ['__class__', '__delattr__', '__doc__', '__getattribute__', '__getstate__', '__hash__', '__init__', '__new__', '__reduce__', '__repr__', '__setattr__', '__slots__', '__str__']
-- Dave Abrahams Boost Consulting http://www.boost-consulting.com
David Abrahams <dave@boost-consulting.com> wrote:
What I'd like from you first is a test case along the lines of any one in libs/python/test (one xxxx.cpp and one xxxx.py) that demonstrates the problem you're trying to solve and fails unless it is solved.
OK. Will this do? (Sorry for pasting directly into the mail, my mailer doesn't support file attachments.) Mind the "Todo" comment in the .cpp file.
>>>>>>>>>> immutable_instance_dict.cpp #include <boost/python.hpp>
using namespace boost::python; class Foo { public: Foo() { a = 42; } int get_a() const { return this->a; } void set_a(int a) { this->a = a; } private: int a; }; BOOST_PYTHON_MODULE(immutable_instance_dict_ext) { // Todo: add a template parameter (or something) to request that // we want instance __dict__s to be immutable class_<Foo>("Foo") .add_property("a", &Foo::get_a, &Foo::set_a) ; } #include "module_tail.cpp" <<<<<<<<<<<<<<<< immutable_instance_dict.cpp
>>>>>>>>>> immutable_instance_dict.py """ from immutable_instance_dict_ext import * f = Foo() f.a 42 f.a = 3 f.a 3 try: ... f.b = 5 ... except AttributeError: ... pass ... else: ... print "Expected AttributeError." ... """
def run(args = None): import sys import doctest if args is not None: sys.argv = args return doctest.testmod(sys.modules.get(__name__)) if __name__ == '__main__': print "running..." import sys status = run()[0] if (status == 0): print "Done." sys.exit(status) <<<<<<<<<<<<<<<< immutable_instance_dict.py -- Stefan Haller Ableton http://www.ableton.com/
participants (3)
-
Andreas Beyer -
David Abrahams -
haller@ableton.com