Search code examples
pythoninheritanceswig

Extending a Python class with the same name and return subclass instance


I have some Python classes being generated automatically using SWIG and I want to extend them. The problem is, those classes have another method other than __init__, called create that returns a class instance (they are the real init).

I want to extend those classes with new methods but keep the original name.

I tried to implement __new__ method but it returns an instance of the parent class

I get why it happens, but I can't seem to find a way to change it while keeping the create methods.

Let me show you an example with my current implementation attempt in order to explain:

File a:

class A:
    def __init__(self, ...):
        pass
    def create(self, ...):
        # do some stuff
        return object_of_A

File b:

from a import A as AClass
class A(AClass):
    def __init__(self, ...):
        pass
    def __new__(cls, ...):
        # do some stuff
        return super(A, cls).create(...)
    def foo(self):
        print('Hi')

Wanted behavior:

>>> from b import A
>>> a = A(...)
>>> a.foo()
>>> Hi

Actual behavior:

>>> from b import A
>>> a = A(...)
>>> a.foo()
>>> AttributeError: 'A' object has no attribute 'foo'

Thanks!


Solution

  • Here goes a simple example. I have created a simple static method for creating objects in C++ and wrapped this with SWIG. Afterwards, I extend the class with the same name for changing the method slightly.

    example.h

    class A {
    public:
      static int Create(A** obj);
      A() = default;
      ~A() = default;
      int test();
    };
    

    example.cpp

    #include "example.h"
    
    int A::test() {
      return 5;
    }
    int A::Create(A** obj) {
      *obj = new A();
      return 0;
    }
    

    example.i

    %module example
    %{
      #include "example.h"
    %}
    
    %typemap(in, numinputs=0) A **obj (A *temp) {
      $1 = &temp;
    }
    
    %typemap(argout) A ** {
      PyObject* temp = NULL;
      if (!PyList_Check($result)) {
        temp = $result;
        $result = PyList_New(1);
        PyList_SetItem($result, 0, temp);
      }
    
      // Create shadow object (do not use SWIG_POINTER_NEW)
      temp = SWIG_NewPointerObj(SWIG_as_voidptr(*$1),
                                $descriptor(A*),
                                SWIG_POINTER_OWN | 0);
    
      PyList_Append($result, temp);
      Py_DECREF(temp);
    }
    
    %include "example.h"
    

    setup.py

    #!/usr/bin/env python
    from distutils.core import setup, Extension
    
    setup(name="example",
          py_modules=['example'],
          ext_modules=[Extension("_example",
                         ["example.i", "example.cpp"],
                         swig_opts=['-c++'],
                                 extra_compile_args=['--std=c++11']
                                 )]
    )
    

    extension.py

    from example import A as parentA
    
    class A(parentA):
      def __init__(self):
        super(A,self).__init__()
      def SmartMethod(self):
        return 2
      @staticmethod
      def Create():
          retval, obj = parentA.Create()
          obj.__class__ = A
          return obj
    

    The new class defined in extension.py inherits all functionalities of the original (if any) and here an example of slightly changing the Create method.

    The trick with changing the __class__ attribute converts the new object into the descendant, so SmartMethod can be called. If you add additional members to the descendant, you must add these in the overloaded Create method.