Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In Cython class, what's the difference of using __init__ and __cinit__?

Code block 1 using __init__

%%cython -3
cdef class c:
    cdef:
        int a
        str s
    def __init__(self):
        self.a=1
        self.s="abc"
    def get_vals(self):
        return self.a,self.s
m=c()
print(m.get_vals())

Code block 2 using __cinit__

%%cython -3
cdef class c:
    cdef:
        int a
        str s
    def __cinit__(self):  # cinit here
        self.a=1
        self.s="abc"
    def get_vals(self):
        return self.a,self.s
m=c()
print(m.get_vals())
  1. I tested both of these codes, and both run without error. In this case, what's the point of using __cinit__ instead of __init__?

  2. I've read the official article, I got confused by one sentence:

    If you need to pass a modified argument list to the base type, you will have to do the relevant part of the initialization in the __init__() method instead, where the normal rules for calling inherited methods apply.

What does "modified argument" mean? Here, why should I use init rather than cinit?

like image 969
o_yeah Avatar asked Jul 10 '20 04:07

o_yeah


2 Answers

It's mainly about inheritance. Suppose I inherit from your class C:

class D(C):
    def __init__(self):
        pass  # oops forgot to call C.__init__

class E(C):
    def __init__(self):
        super().__init__(self)
        super().__init__(self)  # called it twice

How __init__ ends up being called is entirely up to the classes that inherit from it. Bear in mind there may be multiple layers of inheritance.

Additionally, a fairly common pattern for creating classes that wrap C/C++ objects is to create a staticmethod cdef function as an alternative constructor:

cdef class C:
    def __cinit__(self):
        print("In __cinit__")

    @staticmethod
    cdef make_from_ptr(void* x):
        val = C.__new__(C)
        # do something with pointer
        return val

In this case again, __init__ typically isn't called.

In contrast __cinit__ is guaranteed to be called exactly once and this happens automatically by Cython at an early stage of the process. This is most important when you have cdef attributes (such as C pointers) that your class relies on being initialized. It would be impossible for a Python derived class to even set these up, but __cinit__ can ensure that they are.

In your case it probably doesn't matter - use whichever you're happy with.


In terms of "modified arguments" it's saying you can't replicate this with __cinit__:

class NameValue:
     def __init__(self, name, value):
         self.name = name
         self.value = value

class NamedHelloPlus1(NamedValue):
    def __init__(self, value):
        super().__init__("Hello", value+1)

i.e. NamedHelloPlus1 controls what arguments NamedValue gets. With __cinit__ Cython all the calls to __cinit__ receive exactly the same arguments (because Cython arranges the call - you cannot call it manually).

like image 116
DavidW Avatar answered Oct 06 '22 00:10

DavidW


  1. cinit should be used where C level initialization of object is required. Please be careful here, they may not be a fully valid python object yet. However, anything that cannot be done in cinit, needs to happen in init. By this time, all objects are valid python objects.

  2. if I understand it correctly, it points to modifiable arguments in derived class. Arguments list may be modified or parsed differently that the base type init. In such cases init needs to be used. This may be useful in giving you an insight on what I am trying to explain

like image 27
Bhaskar Tallamraju Avatar answered Oct 06 '22 01:10

Bhaskar Tallamraju