Menu

#951 [python] %immutable directive creates a 2nd variable ?

open
nobody
python (260)
5
2022-02-01
2008-09-26
No

Normally, when using an interface file for python like this:

//example.i
%module example
%{
int a;
%}
int a;

the variable a is available in python via the command:

example.cvar.a

and has by default read and write access.

When using the the %immutable directive such as:

//example.i
%module example
%{
int a;
%}

%immutable
int a;

SWIG actually creates a example.cvar.a which is effectively read-only (that's what it's suppose to do)
but also create a another, separate variable example.a which has read-write permissions (not good at all !):

>>> import example
>>> example.a
0.0
>>> example.cvar.a
0.0
>>> example.cvar.a = 3
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: Variable a is read-only.
>>> example.a = 3
>>> example.a
3

Discussion

  • Josh Cherry

    Josh Cherry - 2008-09-26

    I would say this is a feature rather than a bug. You don't actually have write access to the C++ variable. After setting example.a to 3, if you check example.cvar.a you should see that it is still 0.0. Setting example.a merely makes the python name point somewhere else (note that you can set it to anything, not just a number). It's unfortunate that this is possible, but the same is true for anything in the module (if you had a function named func, you could do example.func = 'foo').

     
  • William Fulton

    William Fulton - 2008-09-27

    %pythonnondynamic can be used to prevent this happening for proxy classes. I don't know of a feature for global variables though.

     
  • David Michel

    David Michel - 2008-09-29

    Yes, I agree. The same is true for anything in the module (e.g. we are allowed to do example.func = 'foo'). But then the question is: why is there 2 things created, 'example.cvar.a' *and* 'example.a' ? Without the %immutable command, there is only one variable created, 'example.cvar.a' . What is 'example.a' created for ? I ony found it by mistake as I typed too quickly and forgot the 'cvar'...and it confused me as to why it exits in the first place

     
  • Olly Betts

    Olly Betts - 2008-10-01
    • summary: immutable directive creates a 2nd variable ? --> [python] %immutable directive creates a 2nd variable ?
     
  • Olly Betts

    Olly Betts - 2022-02-01

    This is still the case with git master.

    Adding the %immutable; line results in this difference in the generated example.py:

    @@ -63,4 +63,5 @@
    
    
     cvar = _example.cvar
    +a = cvar.a
    

    The code that results in this seems to be the !assignable condition here in python.cxx:

        if (!builtin && shadow && !assignable && !in_class)
          Printf(f_shadow_stubs, "%s = %s.%s\n", iname, global_name, iname);
    

    There's a similar issue in the -builtin case, but that is implemented via this code:

      SwigPyBuiltin_AddPublicSymbol(public_interface, "a");
    

    generated by:

        if (builtin && shadow && !assignable && !in_class) {
          Printf(f_init, "\t PyDict_SetItemString(md, \"%s\", PyObject_GetAttrString(globals, \"%s\"));\n", iname, iname);
          Printf(f_init, "\t SwigPyBuiltin_AddPublicSymbol(public_interface, \"%s\");\n", iname);
        }
    

    I think this is clearly a bug.

    The idea that we can add the example.a wrapping because of the immutability seems flawed - it's true we don't need to relay updates to the C/C++ variable, but creating a value which is disconnected from the C/C++ value is buggy. Allowing example.a to be modified is one problem, but another is that a in the C/C++ layer might change - consider this:

    %module example
    %{
    int a = 42;
    void set_a(int v) { a = v; }
    %}
    %immutable;
    int a;
    void set_a(int v);
    

    Then:

    >>> import example
    >>> example.cvar.a
    42
    >>> example.a
    42
    >>> example.set_a(1)
    >>> example.cvar.a
    1
    >>> example.a
    42
    
     

Log in to post a comment.