New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Make __dict__ accessible for Python builtin classes #320
Conversation
This looks like an improvement. dir() now show's that the type has a I'm just surprised that a 'dict' is not provided 'for free' and that Some minor comments:
to
|
A dictionnary is provided for free when you set tp_dictoffset to 0 (this is why setting random instance attribute is possible). However, for unknown reason, this dictionnary is not accessible through the dict attribute. The change in SWIG_Python_NewPointerObj() is required to prevent subclasse attributes to be reset when the main classes init() method is called. My commit message show an example of the problem. Moving the dictionnary creation within SWIG_NewPointerObj(), will exhibit the same issue. Given this test case:
The dictionary is overwritten as soon as MySwigWrappedClass.init() is called, which is why I originally implemented the dictionary creation in the getter function. Let me know if you have any idea concerning this issue, otherwise I'll keep looking |
Just came accross the following original Python code, which work exactly the same. Have a look at the subtype_dict function : http://www.opensource.apple.com/source/python/python-3/python/Objects/typeobject.c I thus assume it is the correct way to do it, and that the GIL is protecting the function. |
Attribute set within instance of a SWIG Python wrapped class are stored in SwigPyObject->dict, which tp_dictoffset slot is pointing to. However, SWIG wrapped classes did not have a __dict__ attribute. Inheriting subclasses did not get the attribute either because the SWIG wrapped classes initialize the tp_dictoffset slot: From http://bugs.python.org/issue16272: "If a type defines a nonzero tp_dictoffset, that type is responsible for defining a `__dict__` slot as part of the tp_getset structures. Failure to do so will result in the dict being inaccessible from Python via `obj.__dict__` from instances of the type or subtypes." Provide a SwigPyObject_get___dict__() function to retrieve the dict attribute or create it when it does not exist yet (it is normally created when setting attribute set), and a PyGetSetDef entry pointing to this function.
The following patch attempt to fix a memory leak happening when a random class attribute is set. The internal instance dictionary is created but never freed. This fixes the problem for me, although I am not sure the patch is correct. <code> p = MySWIGClass() p.random_attribute = 0 </code> Valgrind report: ==18267== 280 bytes in 1 blocks are definitely lost in loss record 1,372 of 1,780 ==18267== at 0x4A0645D: malloc (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so) ==18267== by 0x3A90A885DC: PyObject_Malloc (in /usr/lib64/libpython2.7.so.1.0) ==18267== by 0x3A90B101A8: _PyObject_GC_Malloc (in /usr/lib64/libpython2.7.so.1.0) ==18267== by 0x3A90B102AC: _PyObject_GC_New (in /usr/lib64/libpython2.7.so.1.0) ==18267== by 0x3A90A80943: PyDict_New (in /usr/lib64/libpython2.7.so.1.0) ==18267== by 0x3A90A6E8FC: PyFrame_New (in /usr/lib64/libpython2.7.so.1.0) ==18267== by 0x3A90AE1A65: PyEval_EvalCodeEx (in /usr/lib64/libpython2.7.so.1.0) ==18267== by 0x3A90AE088E: PyEval_EvalFrameEx (in /usr/lib64/libpython2.7.so.1.0) ==18267== by 0x3A90AE21DC: PyEval_EvalCodeEx (in /usr/lib64/libpython2.7.so.1.0) ==18267== by 0x3A90AE22E1: PyEval_EvalCode (in /usr/lib64/libpython2.7.so.1.0) ==18267== by 0x3A90AFB71E: ??? (in /usr/lib64/libpython2.7.so.1.0) ==18267== by 0x3A90AFC8DD: PyRun_FileExFlags (in /usr/lib64/libpython2.7.so.1.0) ==18267==
…t__() When a SWIG classes instances is initialized, its internal dictionary was reset to NULL, which result in the loss of any attribute that might have been set for the instance. Only initialize the internal dictionary on actual PyObject creation. class Test(MySwigWrappedClass): def __init__(self): self.val = "Random Value" MySwigWrappedClass.__init__(self) p = Test() print hasattr(p, "val") # Should return True, but used to return False
Okay, I think all the points have been addressed except the changes in SWIG_Python_NewPointerObj should be reverted. I would have said it is bad practice to expect a base class's functionality to work before calling the base's init, but I think the lazy initialization is okay now, so doesn't matter for this use case. |
If you revert the change in SWIG_Python_NewPointerObj(), newobj->dict will be reset to NULL anytime init() is called. Setting random attribute before calling the parent init() function sounds perfectly fine by me ? |
Sorry, I didn't quite follow all of your reply about SWIG_Python_NewPointerObj(), but I understand now. Not calling init first is very unnatural to a c++ programmer where the base class is fully initialized (via constructors calls) before you can modify the derived class. However, your example does actually work with a pure Python class, so mirroring that behaviour is probably best, so I'll merge this as is once the Travis tests have passed. |
Attribute set within instance of a SWIG Python wrapped class are
stored in SwigPyObject->dict, which tp_dictoffset slot is pointing to.
However, SWIG wrapped classes did not have a dict attribute.
Inheriting subclasses did not get the attribute either because the
SWIG wrapped classes initialize the tp_dictoffset slot:
From http://bugs.python.org/issue16272:
"If a type defines a nonzero tp_dictoffset, that type is responsible for
defining a
__dict__
slot as part of the tp_getset structures. Failure todo so will result in the dict being inaccessible from Python via
obj.__dict__
from instances of the type or subtypes."Provide a SwigPyObject_get___dict__() function to retrieve the dict
attribute or create it when it does not exist yet (it is normally
created when setting attribute set), and a PyGetSetDef entry pointing
to this function.