Commit 613da914 authored by gsamain's avatar gsamain

Code snippet of nexedi's cypclass blog article

parent 4af9a38b
cdef cypclass CrazyOperations:
int a
double b
CrazyOperations __iadd__(self, CrazyOperations other):
self.a += other.a
CrazyOperations __add__(self, CrazyOperations other):
return CrazyOperations(self.a, self.b + other.b)
CrazyOperations __isub__(self, CrazyOperations other):
self.b -= other.b
__init__(self, int a = 0, double b = 4.2):
self.a = a
self.b = b
cdef cypclass SuperCrazyOperations(CrazyOperations):
__init__(self, int a = 0, double b = 4.2):
CrazyOperations.__init__(self, a, b)
SuperCrazyOperations __iadd__(self, CrazyOperations other):
CrazyOperations.__iadd__(self, other)
SuperCrazyOperations __iadd__(self, SuperCrazyOperations other):
# This is one (possibly weird) way to chain to base class' __iadd__, done here for the example
casted_self = <CrazyOperations> self
# As this is casted, we will call base __iadd__ with the += below
casted_self += <CrazyOperations> other
self.b = 0
# At the end, we added both a attributes, and set out b to 0.0
SuperCrazyOperations __isub__(self, CrazyOperations other):
CrazyOperations.__isub__(self, other)
SuperCrazyOperations __isub__(self, SuperCrazyOperations other):
CrazyOperations.__isub__(self, other)
self.a = 4
def test():
o1 = SuperCrazyOperations(1, 2)
o2 = CrazyOperations(42, 4.2)
o3 = SuperCrazyOperations(-3, 2.1)
# This should print:
# 1 2.0
print(o1.a, o1.b)
# This should print:
# 42 4.2
print(o2.a, o2.b)
# This should print:
# -3 2.1
print(o3.a, o3.b)
# We're calling __add__ so we just add the b attributes
tmp1 = o1 + o2
# This should print:
# 1 6.2
print(tmp1.a, tmp1.b)
# CrazyOperations' __iadd__ is called: adding the a attributes
o2 += o1
# This should print:
# 43 4.2
print(o2.a, o2.b)
# SuperCrazyOperations' __iadd__ is called: adding the a attributes AND setting own (o1) b to 0.0
o1 += o3
# This should print:
# -2 0
print(o1.a, o1.b)
# We're calling CrazyOperations' __isub__, so we're substracting b attributes
o1 -= o2
# This should print:
# -2 -4.2
print(o1.a, o1.b)
# We're calling SuperCrazyOperations' __isub__, so we're substracting b attributes AND setting own (o1) a to 4
o1 -= o3
# This should print:
# 4 -6.3
print(o1.a, o1.b)
\ No newline at end of file
cdef extern from "SomeCppHeader.h" nogil:
cdef cppclass SomeDeclaredCppClass nogil:
int getter()
cdef cppclass SomeDefinedCppClass nogil:
int value
int getter():
return this.value
void __init__():
this.value = 42
cdef int do_test() nogil:
cdef SomeDefinedCppClass* heap_allocated_cpp_object = new SomeDefinedCppClass()
val = heap_allocated_cpp_object.getter()
del heap_allocated_cpp_object
return val
cpdef void test():
print(do_test())
cdef struct SomeCStruct:
# This type doesn't need the GIL
int a # This is a C int
double b # This is a C double
class SomePythonClass:
# This type needs the GIL
def __init__(self):
a = 3 # This is a PyObject
cdef class SomeCythonClass:
# This type needs the GIL
cdef object a # This is a PyObject
cdef int b # This is a C int
def __init__(self):
a = 3 # This is a PyObject
def test():
# This function must hold the GIL to be able to instantiate the SomeCythonClass object
cdef SomeCStruct stack_allocated_struct
stack_allocated_struct.a = 42
stack_allocated_struct.b = 4.2
heap_allocated_cython = SomeCythonClass()
heap_allocated_cython.b = stack_allocated_struct.a # This is a C assignation (both are C int)
heap_allocated_cython.a = stack_allocated_struct.b # The C double is coerced to a PyObject before assignation
print(heap_allocated_cython.a, heap_allocated_cython.b)
cdef cypclass Test:
int a
cpdef void test():
heap_allocated_test = Test()
del heap_allocated_test
cdef cypclass A:
int a
cypclass B:
int b
__init__(self, int b):
self.b = 2*b
B b
__init__(self, int a, int b):
self.a = a
self.b = B(b)
cpdef void test():
a = A(2, 2)
b = A.B(3)
print(a.a, a.b.b, b.b)
cdef cypclass A:
int a
int getter(self):
return self.a*6
int __int__(self):
return self.getter()
__init__(self, int a):
self.a = a-1
cdef cypclass MyClass:
# Cypclass object are initialized to NULL by default.
# This is done in the C++ construction part.
A cypobj
int number
__init__(self, int a=0):
self.cypobj = A(a)
self.number = a
__init__(self, A obj):
self.cypobj = obj
self.number = obj.a+1
int get_A_value(self):
if self.cypobj is not NULL:
return self.cypobj.getter()
else:
return 42
cdef int take_value(MyClass o) nogil:
value = o.get_A_value()
return value
def print_values():
# Not a cdef [...] nogil one because I'm tired of all the with gil to use Python's print
method1_specified = MyClass(2)
method1_default = MyClass()
method2 = new MyClass()
print(take_value(method1_specified), take_value(method1_default), take_value(method2))
cdef cypclass Test:
int a
cpdef void test():
heap_allocated_test = Test()
heap_allocated_test = <Test> NULL
cdef cypclass OK:
"""
This is OK because the wrapper will pass a & b from __new__ to __init__ directly,
and will quite blindly transfer the information about c presence in __new__ to __init__
"""
__init__(self, int a, double b = 4.2, int c = 42):
pass
OK __new__(alloc, int a, double b, int c = 0):
return alloc()
cdef cypclass KO:
"""
This will fail because the wrapper knows b (in __new__) is optional,
but doesn't know its default value, so it cannot pass it to __init__
"""
__init__(self, int a, double b, int c = 42):
pass
KO __new__(alloc, int a, double b = 4.2, int c = 0):
return alloc()
cdef void main() nogil:
o1 = OK(2, 2.4)
o2 = KO(3, 2.1)
cdef cypclass Base:
int getter(self):
return 4
cdef cypclass Derived(Base):
int getter(self):
return 2
def print_42():
o = Derived()
print(str(Base.getter(o)) + str(o.getter()))
return
class Class:
def __init__(self):
self.a = 3
cdef test(int a=0):
return a
def main():
cls = Class()
s = test()
cdef cypclass Base:
int base
cdef cypclass Derived1(Base):
int derived1
void setBase(self, int arg):
self.base = arg
cdef cypclass Derived2(Base):
int derived2
void setBase(self, int arg):
self.base = arg
cdef cypclass Diamond(Derived1, Derived2):
int diamond
cdef void printD1(Derived1 d1) with gil:
print(d1.derived1, d1.base)
cdef void printD2(Derived2 d2) with gil:
print(d2.derived2, d2.base)
cpdef void test():
o = Diamond()
o.derived1 = 42
o.derived2 = 4242
Derived1.setBase(o, 4)
Derived2.setBase(o, 2)
printD1(o)
printD2(o)
cdef cypclass Base:
double value
__init__(self, int a):
self.value = (<double> a)*1.2
__init__(self, double b):
self.value = b
Base __new__(alloc, int a):
obj = alloc()
# After the return, the call Base.__init__(obj, a) will be issued
return obj
cdef cypclass Derived(Base):
Derived __new__(alloc, double b):
obj = alloc()
# After the return, the call Base.__init__(obj, b) will be issued
return obj
cpdef void test():
base = Base(5)
derived = Derived(5)
# Should print: 6 5
print(base.value, derived.value)
cdef cypclass SomeClass:
int a
double b
int __int__(self):
return self.a
double __double__(self):
return self.b
cdef cypclass SomeContainer:
SomeClass some_object
bint __bool__(self):
return self.some_object is not NULL
int __int__(self):
if self:
return <int> self.some_object
else:
return 0
double __double__(self):
if self:
return <double> self.some_object
else:
return 0.0
cpdef void test():
contained = SomeClass()
contained.a = 42
contained.b = 4.2
container = SomeContainer()
container.some_object = contained
print(<bint> container, <int> container, <double> container)
cdef cypclass AheadOfTimeChunk
DEF CHUNK_SIZE = 2
cdef AheadOfTimeChunk chunk[CHUNK_SIZE]
cdef unsigned int chunk_started = 0
# chunk_allocated[index] tells us the state of memory pointed by chunk[index]
cdef int chunk_allocated[CHUNK_SIZE]
cdef AheadOfTimeChunk chunkclass_singleton
cdef bint chunkclass_singleton_allocated = False
cdef cypclass AheadOfTimeChunk:
"""
A stupid chunk allocator:
Whenever the user requests a new object, fills each empty cell of
the chunk array with fresh memory. Give to the user the first
available chunk cell with allocated but unused memory.
This class is reaaally stupid because it still frees memory when
the user don't need the object anymore, so the __alloc__ will be filling
again and again the cells we just deallocated with fresh memory.
"""
int cell_index
__dealloc__(self):
global chunkclass_singleton_allocated
global chunkclass_singleton
global chunk_allocated
idx = self.cell_index
if idx >= 0:
with gil:
print("Object destruction : mark cell", idx, "as empty")
chunk_allocated[idx] = -1
else:
with gil:
print("Object destruction: out-of-chunk deallocation")
if self is chunkclass_singleton:
with gil:
print(" Object destruction : Mark singleton flag as unallocated")
chunkclass_singleton_allocated = False
chunkclass_singleton = <AheadOfTimeChunk> NULL
AheadOfTimeChunk __new__(alloc, bint singleton = False, bint must_fit_in_chunk = False):
global chunkclass_singleton_allocated
global chunkclass_singleton
if singleton and chunkclass_singleton_allocated:
with gil:
print("Object creation : Returning already allocated singleton")
obj = chunkclass_singleton
else:
obj = alloc()
if obj is NULL and not must_fit_in_chunk:
with gil:
print("Object creation : Allocating object outside of chunk")
obj = new AheadOfTimeChunk()
obj.cell_index = -1
if obj is not NULL and singleton:
with gil:
print("Object creation : Singleton allocated")
chunkclass_singleton_allocated = True
chunkclass_singleton = obj
# We should decref here (assignment to chunkclass_singleton has incref'ed obj, we do not want that)
del chunkclass_singleton
if obj is NULL:
# This is done to show something for the demo
with gil:
print("Object creation : Obj is NULL !")
return obj
AheadOfTimeChunk __alloc__():
"""
Convention for chunk_allocated is:
1 - Memory in use
0 - Memory allocated and available
-1 - No memory allocated
"""
global chunk_started
global chunk_allocated
global chunk
if not chunk_started:
for i in range(CHUNK_SIZE):
chunk_allocated[i] = -1
chunk_started = 1
cdef AheadOfTimeChunk ptr = <AheadOfTimeChunk> NULL
for i in range(CHUNK_SIZE):
if chunk_allocated[i] == -1:
with gil:
print("cell", i, "is empty: allocate it")
chunk[i] = new AheadOfTimeChunk()
chunk[i].cell_index = i
chunk_allocated[i] = 0
if ptr is NULL and chunk_allocated[i] == 0:
with gil:
print("cell", i, "is allocated but unused: take it")
chunk_allocated[i] = 1
ptr = chunk[i]
# We must decref ptr here, because it is the same reference
del ptr
return ptr
cpdef void testChunk():
print("=== Creating a basic object ===")
basic_obj = AheadOfTimeChunk()
print()
print("=== Getting singleton (no previous references) ===")
singleton_obj1 = AheadOfTimeChunk(True)
print()
print("=== Getting singleton (one existing reference) ===")
singleton_obj2 = AheadOfTimeChunk(True)
if singleton_obj1 is singleton_obj2 and singleton_obj1 is chunkclass_singleton:
print(" Both singleton pointers are correct")
print()
print("=== Deleting one singleton reference (one ref left) ===")
#del singleton_obj1
singleton_obj1 = <AheadOfTimeChunk> NULL
print()
print("=== Getting again singleton (one existing reference) ===")
singleton_obj3 = AheadOfTimeChunk(True)
if singleton_obj3 is singleton_obj2:
print(" Freeing a singleton instance doesn't crash things")
print()
print("=== Allocating an object outside of chunk ===")
outside_allocation = AheadOfTimeChunk()
print()
print("=== Make a fail allocation ===")
failed_outside_allocation = AheadOfTimeChunk(False, True)
# Make some place in the chunk to see reuse
print()
print("=== Deleting one singleton reference (one ref left) ===")
#del singleton_obj3
singleton_obj3 = <AheadOfTimeChunk> NULL
print()
print("=== Deleting one singleton reference (zero refs left) ===")
#del singleton_obj2
singleton_obj2 = <AheadOfTimeChunk> NULL
print()
print("=== Allocating again a basic object (inside the chunk) ===")
inside_chunk = AheadOfTimeChunk()
# We can afford a very specific test here
if inside_chunk.cell_index == 1 and inside_chunk is chunk[1]:
print(" Chunk cell reuse went fine")
print()
print("=== Allocating singleton outside of chunk (no previous references) ===")
outside_singleton = AheadOfTimeChunk(True)
print()
print("Remaining objects at the end of this function are:")
print("1 The first object we created (living at cell 0)")
print("2 The first out-of-chunk allocated object")
print("3 The last successfully non-singleton allocated object (living at cell 1)")
print("4 One singleton instance (living outside of chunk)")
print("The object destruction messages should reflect this order")
print()
cdef cypclass Base:
int a
int b
__init__(self, int a, int b):
self.a = a
self.b = b
__init__(self, int a):
self.__init__(a, 0)
__init__(self):
self.__init__(0, 0)
cdef cypclass Derived(Base):
int c
__init__(self, int a, int b, int c):
self.c = c
cdef cypclass AltDerived(Base):
__init__(self, int a, int b):
#self.a = a + b
self.b = b - a
cpdef void test():
cdef Base base = Base(4, 2)
cdef Derived derived1 = Derived(4)
cdef Derived derived2 = Derived(4, 2)
cdef Derived derived3 = Derived(4, 2, 1)
cdef AltDerived alt_derived = AltDerived(4, 2)
# Base should be correctly set
print(base.a, base.b)
# derived1 has undefined c, so derived1.c will be garbage
print(derived1.a, derived1.b, derived1.c)
# Same thing for derived2, but derived2.b is not zero
print(derived2.a, derived2.b, derived2.c)
# derived3 has the opposite behaviour: c is set, a and b are garbage
print(derived3.a, derived3.b, derived3.c)
# alt_derived won't output 4 2 but garbage and -2
print(alt_derived.a, alt_derived.b)
cdef cypclass Singleton
cdef int allocated = 0
cdef Singleton ptr
cdef cypclass Singleton:
Singleton __new__(alloc):
global allocated
global ptr
if not allocated:
ptr = alloc()
allocated = 1
return ptr
cpdef void test():
o1 = Singleton()
o2 = Singleton()
if o1 is o2 and o1 is ptr:
print("Everything is fine")
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment