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

Viewed 392

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!

1 Answers

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.

Related