Question

At the bottom are two files, one super minimal python file which should be executed and one cython file. If you save them as files name the cython one "cycode.pyx" and it will compile and run automatically once you execute the other file (e.g. "start.py")

The Problem

If you execute the pure python file /.start.py you will get an attribute error from Cython.

Exception AttributeError: "'cycode.Item' object has no attribute 'export'" in 'cycode.insertItem'

From my experience that means that a Python function or object tries to access cython code which is not declared public (or cpdef, readonly, def etc.). But I never intended this function to be accessed from Python. As far as I can see this should not happen. There should be a clean separation between cython and python. Python only gets a list with simple dicts.

The question is why that happens? My goal is not to just make it work, this could be done with a simple cpdef. But to understand why this happened and, ultimately, how to send data from cython to python in a clean and controlled way, without having to declare any cython object public for the python realm.

start.py

    #! /usr/bin/env python3
    # -*- coding: utf-8 -*-
    import pyximport; pyximport.install()
    import cycode
    #Register a callback function with the cython module.
    #In this case just attempt to print the data.
    cycode.callbacksDatabase.update.append(print)


    #Call an insert function to create and insert a cython object.
    #We should have nothing to do with this object,
    #we just receive a simple list of dict(s) via the callback.
    cycode.new1() 

cycode.pyx

# cython: language_level=3
cdef class Block:
    """A container class for Items"""
    cdef list content
    cdef void insert(self, Item item)
    cdef list export(self)    

    def __cinit__(self):                
        self.content = []

    cdef void insert(self, Item item):        
        self.content.append(item)           

    cdef list export(self):
        """The frontend should just receive simple data types to
        vizualize them. Create export dicts of all items"""
        cdef list result        
        result = []
        for item in self.content:            
            result.append(item.export())  #THIS is the problem. item.export() cannot be found.
        return result        

cdef class Item:
    cdef int value
    cdef dict export(self)
    def __cinit__(self, int value):
        self.value = value

    cdef dict export(self):        
        return {
            "id" : id(self),
            "value" : self.value,
        }

########API#############
class Callbacks():    
    def __init__(self):
        self.update = []        

    def _update(self):
        ex = block.export()
        for func in self.update:            
            func(ex)

cdef void insertItem(int value):        
    cdef Item item
    item = Item(value)  #this should create a cython object, not a python one.
    block.insert(item)            
    callbacksDatabase._update()        

def new1():    
    insertItem(1)

#######Actual Data on module level#######
cdef Block block
block = Block() #this is just for the cython code. No direct access from python allowed.
callbacksDatabase = Callbacks()  #this should be accesable from python
Was it helpful?

Solution

Typical IRC effect... once you work out the question in detail the solution pops up a few minutes later (through facebook chat this time though...).

I forgot that in Python/Cython a for loop does not automagically create a new scope and that the loop variable is nothing special. It needs to be declared as cdef as well.

    cdef Item item
    for item in self.content:            
        result.append(item.export())
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top