views:

254

answers:

2

I have a plugin written entirely in Python using PyObjC whose core classes I need to convert to Objective-C. One of them basically just loads up a Python module and executes a specific function, passing it keyword arguments. In PyObjC, this was extremely.

However, I'm having difficulty figuring out how to do the same thing using the Python C API. In particular, I'm unsure how best to convert an NSDictionary (which might hold integers, strings, booleans, or all of the above) into a format that I can then pass on to Python as keyword arguments.

Anyone have pointers on how to accomplish something like this? Thanks in advance!

Edit: just to clarify, I'm converting my existing class which was formerly Python into Objective-C, and am having trouble figuring out how to move from an NSDictionary in Objective-C to a Python dictionary I can pass on when I invoke the remaining Python scripts. The Objective-C class is basically just a Python loader, but I'm unfamiliar with the Python C API and am having trouble figuring out where to look for examples or functions that will help me.

+1  A: 

This is the function I use. Not sure if it's the most efficient way to do it but it should help get you started:

id PyObjectToObjc(PyObject *object)
{
    if (object == NULL) {
        NSLog(@"WTF? Object is NULL!");
        return nil;
    } else if (PyString_Check(object)) {
        return [NSString stringWithUTF8String:PyString_AS_STRING(object)];
    } else if (PyUnicode_Check(object)) {
        PyObject *str = PyUnicode_AsUTF8String(object);
        NSString *ret = PyObjectToObjc(str);
        Py_DECREF(str);
        return ret;
    } else if (PyInt_Check(object)) {
        return [NSNumber numberWithLong:PyInt_AS_LONG(object)];
    } else if (PyLong_Check(object)) {
        return [NSNumber numberWithLong:PyLong_AsLong(object)];
    } else if (PyList_Check(object) || PyTuple_Check(object)) {
        int isTuple = PyTuple_Check(object);
        Py_ssize_t len = isTuple ? PyTuple_Size(object) : PyList_Size(object);
        NSMutableArray *list = [[NSMutableArray alloc] initWithCapacity:len];
        Py_ssize_t i = 0;

        // Create function pointer to the correct GetItem function
        PyObject *(*GetArrayItem)(PyObject *, Py_ssize_t);
        GetArrayItem = isTuple ? &PyTuple_GetItem : &PyList_GetItem;

        for (i = 0; i < len; i++) {
            id objcObject = PyObjectToObjc(GetArrayItem(object, i));
            if (objcObject != nil) {
                [list addObject:objcObject];
            } else {
                NSLog(@"Error in PyObjectToObjc: Item %i was nil in list %@",
                      i, list);
            }
        }

        // Create non-mutable copy of array if list is tuple
        if (isTuple) {
            NSArray *copy = [list copy];
            [list release];
            return [copy autorelease];
        }

        return [list autorelease];
    } else if (PyAnySet_Check(object)) {
        Py_ssize_t len = PySet_Size(object);
        NSMutableArray *set = [[NSMutableArray alloc] initWithCapacity:len];
        unsigned i = 0;

        for (i = 0; i < len; i++) {
            PyObject *poppedObj = PySet_Pop(object);
            id objcObject = PyObjectToObjc(poppedObj);
            Py_DECREF(poppedObj);
            if (objcObject != nil) {
                [set addObject:objcObject];
            } else {
                NSLog(@"Error in PyObjectToObjc: Item %i was nil in set %@",
                      i, set);
            }
        }

        NSArray *copy = [set copy];
        [set release];
        return [copy autorelease];
    } else if (PyDict_Check(object)) {
        PyObject *pyKeys = PyDict_Keys(object);
        id keys = PyObjectToObjc(pyKeys);
        Py_DECREF(pyKeys);

        PyObject *pyValues = PyDict_Values(object);
        id values = PyObjectToObjc(pyValues);
        Py_DECREF(pyValues);

        return [NSDictionary dictionaryWithObjects:values forKeys:keys];
    } else if (PyBool_Check(object)) {
        return [NSNumber numberWithBool:object == Py_True];
    } else if (PyFloat_Check(object)) {
        return [NSNumber numberWithDouble:PyFloat_AsDouble(object)];
    } else {
        NSLog(@"Could not convert PyObject; returning nil.");
        return nil;
    }
}
Michael
Sorry, I must have been unclear: I need to start with an NSDictionary and convert it to a Python dict (so I can hand it off to Python as a keyword argument dictionary). Thanks for the code, though! That'll be really useful if I ever need to migrate in the other direction.
One Crayon
+2  A: 

Oh, looks like I misunderstood your question. Well, going the other direction isn't terribly different. This should be (as least a start of) the function you're looking for (I haven't tested it thoroughly though, so beware of the bugs):

// Returns a new reference
PyObject *ObjcToPyObject(id object)
{
    if (object == nil) {
     // This technically doesn't need to be an extra case, 
     // but you may want to differentiate it for error checking
     return NULL;
    } else if ([object isKindOfClass:[NSString class]]) {
     return PyString_FromString([object UTF8String]);
    } else if ([object isKindOfClass:[NSNumber class]]) {
     // You could probably do some extra checking here if you need to
     // with the -objCType method.
     return PyLong_FromLong([object longValue]);
    } else if ([object isKindOfClass:[NSArray class]]) {
     // You may want to differentiate between NSArray (analagous to tuples) 
     // and NSMutableArray (analagous to lists) here.
     Py_ssize_t i, len = [object count];
     PyObject *list = PyList_New(len);
     for (i = 0; i < len; ++i) {
      PyObject *item = ObjcToPyObject([object objectAtIndex:i]);
      NSCAssert(item != NULL, @"Can't add NULL item to Python List");
      // Note that PyList_SetItem() "steals" the reference to the passed item.
      // (i.e., you do not need to release it)
      PyList_SetItem(list, i, item);
     }
     return list;
    } else if ([object isKindOfClass:[NSDictionary class]]) {
     PyObject *dict = PyDict_New();
     for (id key in object) {
      PyObject *pyKey = ObjcToPyObject(key);
      NSCAssert(pyKey != NULL, @"Can't add NULL key to Python Dictionary");
      PyObject *pyItem = ObjcToPyObject([object objectForKey:key]);
      NSCAssert(pyItem != NULL, @"Can't add NULL item to Python Dictionary");
      PyDict_SetItem(dict, pyKey, pyItem);
      Py_DECREF(pyKey);
      Py_DECREF(pyItem);
     }
     return dict;
    } else {
     NSLog(@"ObjcToPyObject() could not convert Obj-C object to PyObject.");
     return NULL;
    }
}

You may also want to take a look at the Python/C API Reference manual if you haven't already.

Michael
Thanks Michael! I looked through the C API before I posted the question, but I couldn't figure out how to leverage the PyDict methods with Objective-C NS objects (still dusting off my C; been a while since I had to think about pointers and so forth). Thanks for the starting code!
One Crayon