How to Import Python Modules in Server Side Swift

Rocky WEI
Server Side Swift and More
5 min readSep 1, 2017

When I was developing Perfect-TensorFlow: an open sourced Swift language binding for Google’s TensorFlow, an idea just popped out: is it possible to directly “borrow” some Python modules into Swift code? I was scared a bit firstly by my own idea, but… why not? If there was a ready library with a clean and nice API, I could probably have saved a huge amount of time in code development, and not mention keeping the stability while expanding Swift’s capabilities gracefully in the same time.

I was not surprised after some research, although the solution is a bit different from my previous multi-language integration experience. Yes, as far as I know, C source can be planted directly into Swift obviously, and C++ source can migrate into Swift for both MacOS / Linux, either (by some tricks to wrap C++ with C API), as the demo projects listed in my personal github repo:

  • “CSwift”: How to integrate C code into a Swift Project including Swift REPL script to call dynamic libraries.
  • “csweet”: Demo project for umbrella C++ into Swift.

However, the “Perfect-Python” — a Swift / Python integration solution as a standard Swift library, proudly provided by PerfectlySoft Inc, has been implemented in a different design pattern, totally, because “swift build” doesn’t support python building in both terms of script interpreter and binary code compiler.

Perfect-Python is using Python runtime as a solution, i.e, Object Interface Mapping.

Unified PyObj Class

Firstly, let’s take a look at how to import a Python module. Assume that there is a Python script “/tmp/clstest.py”, now I can use Perfect-Python in Swift like this:

// Prior to the importing, modify Package.swift with the following dependencies:
// .Package(url: "https://github.com/PerfectlySoft/Perfect-Python.git", majorVersion: 1)
import PythonAPI
import PerfectPython
// initialize python runtime, for once.
Py_Initialize()

// import a python module from the given path:
let pymod = try PyObj(path: "/tmp", import: "clstest")

In this case, the Python Module has been attached to a PyObj class instance. “PyObj” is a Swift class that can delegate all Python objects, literally, in the PerfectPython namespace / context.

This means that a PyObj can be:

  • a module
  • a variable
  • a class definition
  • an object instance
  • a function
  • a class property, or
  • a class method, and especially, my favourite part is,
  • you can extend PyObj as much as you want, in a Swift manner.

The point is that you can use this concept recursively, which means that each PyObj instance may have endless children PyObj, and each child / grand child / grand grand child and so on, can be anything in the above list.

I know that this explanation is too general to undersand and hard to visualize, so let me present some demo codes here.

Assume that a python file “/tmp/clstest.py” has the following source code: an assignment from a constant string to a variable.

// this is a python script
stringVar = 'Hello, world'

As I have already had the “pymod”, the python module of “clstest” in form of PyObj, it is possible to access this “stringVar” variable now, by looking up a sub-PyObj of the “pymod”:

// this is the swift calling
if let str = pymod.load("stringVar")?.value as? String {
print(str)
// will print it out as "Hello, world!"
}

In this example, “pymod” is the root object because it has been initialized from a “import” constructor, then a global variable in the python script can be treated as a child of this module. By this mean, the example just finished an object mapping from Python to Swift.

Perfect-Python has provided some basic conversions between Swift and Python, by implementing PyObj constructors and using “PyObj().value”:

  • Initialize a PyObj from a Swift String:
    let pystr = try PyObj(value:"Hello")
  • Get a Swift String value from a PyObj:
    let str = pystr.value as? String

Function Calls

As mentioned, you can access any functions in a module, or a PyObj. Given a typical python function below:

// this is a python function to perform a multiplication.
def mymul(num1, num2):
return num1 * num2

To call this function, simply using PyObj instance method “call()” with two key items: function name as a string, and parameters as an array “[Any]”:

if let res = pymod.call("mymul", args: [2,3])?.value as? Int {
print(res)
// the result will be 6
}

The result of call() is still a PyObj, so you can convert its value to the objective Swift type as need.

Class Object

Now you can imagine the similar mechanism should work for PyObj loading from a Python class, however, the tricky part is to understand “Class” and “Instance” should be loaded as different PyObj variables separately in practice.

To explain how it works, again, use the following snippet as a typical Python class:

// class definition in Python
class Person:
def __init__(self, name, age):
self.name = name
self.age = age

def intro(self):
return self.name + str(self.age)

In this class, “Person” has only one constructor, with two properties “name” and “age”, and a method called “intro()”.

So the Swift strategy is to break down the object mapping into three phases. Step one, load the class as a type:

// Swift Step One: lookup the class name
let personClass = pymod.load("Person")

Step two, initialize an instance with two required parameters:

// Swift Step Two: initialize the class as an object instance
let person = personClass.construct(["rocky", 24])
// person is now the object instance

Now, step three — you can use all properties and methods of this class object instance as the children of the current PyObj:

// Swift Step Three, load / call the descendants of the instance
if let name = person.load("name")?.value as? String,
let age = person.load("age")?.value as? Int,
let intro = person.call("intro", args: [])?.value as? String {
print(name, age, intro)
}

Callbacks

Last but not the least, how to use callbacks in PerfectPython? — Python’s callbacks, certainly.

Consider the following python code as you can execute a function as a parameter like x = caller('Hello', callback):

// the caller function can perform any callbacks:
def callback(msg):
return 'callback: ' + msg
def caller(info, func):
return func(info)

The equivalent Swift code is nothing special but using the objective callback function as an argument before calling:

if let fun = pymod.load("callback"),
let result = pymod.call("caller", args: ["Hello", fun]),
let v = result.value as? String {
print(v)
// it will be "callback: Hello"
}

Summary

Perfect-Python provides a handy API to interact Swift app with Python modules by implementing a Swift class “PyObj”, which can transform to any Python expressions back and forth. Without knowing too much knowledge of Python syntax, you can easily use Python modules, functions and objects in Server Side Swift by applying Perfect-Python into your Swift project, seamlessly, rapidly, and efficiently.

Resources

--

--