Skip to content
Snippets Groups Projects
Commit 8ed0c03e authored by David Gobbi's avatar David Gobbi
Browse files

Warn when default __init__() modifies C++ attributes

If the C++ object already existed within VTK, and the Python object
is being created because it is being returned by a method, then we
don't want the creation of the Python object to cause changes to
C++ object attributes that were already set by the C++ VTK code.

For example, if we are getting the output dataset from a C++ VTK
filter, we don't want any C++ attributes of the dataset to change
when the Python part of this pre-existing dataset object is created.
parent 35fb2ed4
No related branches found
No related tags found
No related merge requests found
......@@ -22,7 +22,9 @@ from vtkmodules.vtkCommonCore import (
vtkObjectBase,
vtkPoints,
)
from vtkmodules.vtkCommonDataModel import vtkImageData
from vtkmodules.vtkFiltersSources import vtkSphereSource
from vtkmodules.vtkImagingSources import vtkImageGridSource
from vtkmodules.test import Testing
class vtkCustomObject(vtkObject):
......@@ -60,6 +62,13 @@ class vtkPointsCustom(vtkPoints):
def __init__(self):
self.some_attribute = "custom"
class vtkImageDataWarning(vtkImageData):
def __init__(self, spacing=(1.0, 1.0, 1.0)):
# this sets the spacing when the constructor is called without any
# arguments, which generates a warning in testOverrideWarning (see
# the test code below for an explanation)
self.SetSpacing(spacing)
class TestSubclass(Testing.vtkTest):
def testSubclassInstantiate(self):
"""Instantiate a python vtkObject subclass"""
......@@ -130,5 +139,25 @@ class TestSubclass(Testing.vtkTest):
vtkPoints.override(None)
self.assertTrue(vtkPoints().__class__ == vtkPoints)
def testOverrideWarning(self):
"""Check if a late call to __init__() modifies the C++ object"""
# check that the object has the correct class
vtkImageData.override(vtkImageDataWarning)
self.assertTrue(isinstance(vtkImageData(), vtkImageDataWarning))
# check object created deep in c++
source = vtkImageGridSource()
source.SetDataExtent(0, 255, 0, 255, 0, 0)
source.SetDataSpacing(0.1, 0.1, 1.0)
# calling Update() instantiates the data object in C++
source.Update()
# calling GetOutput() instantiates the data object in Python,
# and reports RuntimeWarning because our override modifies the data
with self.assertWarns(RuntimeWarning):
data = source.GetOutput()
# the custom __init__() method modified the spacing to be (1.0,1.0,1.0),
# the purpose of the RuntimeWarning is to let the user know that an odd
# modification like this has occurred
self.assertEqual(data.GetSpacing(), (1.0, 1.0, 1.0))
if __name__ == "__main__":
Testing.main([(TestSubclass, 'test')])
......@@ -710,6 +710,13 @@ PyObject* PyVTKObject_FromPointer(PyTypeObject* pytype, PyObject* ghostdict, vtk
}
else if (ghostdict == nullptr && pytype->tp_init != nullptr)
{
// For checking if Python __init__ call modifies the C++ object
vtkObject* checkptr = vtkObject::SafeDownCast(ptr);
vtkMTimeType checktime = 0;
if (checkptr)
{
checktime = checkptr->vtkObject::GetMTime();
}
// Call __init__(self)
PyObject* arglist = Py_BuildValue("()");
int res = pytype->tp_init((PyObject*)self, arglist, nullptr);
......@@ -719,6 +726,23 @@ PyObject* PyVTKObject_FromPointer(PyTypeObject* pytype, PyObject* ghostdict, vtk
Py_DECREF(self);
self = nullptr;
}
else if (checkptr && checktime < checkptr->vtkObject::GetMTime())
{
// If the C++ object already existed within VTK, and the Python object
// is being created right now, then we don't want the creation of the
// Python object to cause changes to object attributes that were already
// set by the C++ VTK code. For example, if we're getting the output
// dataset from a C++ VTK filter, we don't want any C++ attributes of
// the dataset to change when the Python part of the dataset object is
// created and initialized.
std::string message = "Python method ";
message += pytype->tp_name;
message += ".__init__() ";
message += "unexpectedly modified pre-existing C++ base object ";
message += checkptr->GetObjectDescription();
message += ".";
PyErr_WarnEx(PyExc_RuntimeWarning, message.c_str(), 1);
}
}
return (PyObject*)self;
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment