Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Using Swift libraries in Python

Using Swift libraries in Python

So... this is obviously totally, 100%, like for. real. not. supported. by. Apple. …yet?

But still... I thought it was pretty badass. And, seeing how there's already a Swift buildpack for Heroku you could move some slow code into Swift can call it as a library function. But, you know, not in production or anything. That would be silly, right?

Now, having said that, the actual Python/Swift interop may have bugs. I'll leave that as an exercise to the reader.

How to get Python code calling Swift functions:

  1. Get an Ubuntu 15.10 system running. These instructions may also work on other versions of Ubuntu (and Heroku uses Ubuntu server).

  2. Install swift per the directions for Ubuntu 15.10 (or with heroku, just add the heroku buildback)

  3. Install dependencies: sudo apt-get install python-dev

  4. Wherever you put your copy of swift (which you unzipped in the installation instruction)…

    cd SWIFT-ROOT/usr/lib/swift/
    mkdir python
    touch module.map
  5. Copy in the contents of the module.map file below (into YOUR-SWIFT-ROOT/usr/lib/swift/python/module.map)

  6. TADA! Now you can create the sit.swift and sit.py files anywhere you want (but in the same directory with each other) and compile them like so:

# creates "libsit.so" (or on OS X, "libsit.dylib")
swiftc -emit-library sit.swift

Now this works!

$ python sit.py
Hamburger Hamburger
Bang! Bang!

Unfortunately there isn't a way to avoid the crazy mangled names in the Python code right now (that I know of). So as you create Swift functions, you'll have to find the mangled name using nm:

nm libsit.so | grep YOUR_SWIFT_FN_NAME

…again replacing .so with .dylib on OS X. Note: on OS X you get two underscores as a prefix, but you have to remove one for it to work in Python.

One final note… for some reason the mangled names are different on OS X and Linux.

framework module Python [extern_c] [system] {
umbrella header "/usr/include/python2.7/Python.h"
export *
module * { export * }
explicit module bitset {
header "/usr/include/python2.7/bitset.h"
export *
}
explicit module bytes_methods {
header "/usr/include/python2.7/bytes_methods.h"
export *
}
explicit module cStringIO {
header "/usr/include/python2.7/cStringIO.h"
export *
}
explicit module datetime {
header "/usr/include/python2.7/datetime.h"
export *
}
explicit module errcode {
header "/usr/include/python2.7/errcode.h"
export *
}
explicit module frameobject {
header "/usr/include/python2.7/frameobject.h"
export *
}
explicit module graminit {
header "/usr/include/python2.7/graminit.h"
export *
}
explicit module grammar {
// rdar://problem/19484773
requires !cplusplus
header "/usr/include/python2.7/grammar.h"
export *
}
explicit module longintrepr {
header "/usr/include/python2.7/longintrepr.h"
export *
}
explicit module marshal {
header "/usr/include/python2.7/marshal.h"
export *
}
explicit module metagrammar {
header "/usr/include/python2.7/metagrammar.h"
export *
}
explicit module node {
header "/usr/include/python2.7/node.h"
export *
}
explicit module opcode {
header "/usr/include/python2.7/opcode.h"
export *
}
explicit module osdefs {
header "/usr/include/python2.7/osdefs.h"
export *
}
explicit module parsetok {
// rdar://problem/19484773
requires !cplusplus
header "/usr/include/python2.7/parsetok.h"
export *
}
explicit module pgen {
// rdar://problem/19484773
requires !cplusplus
header "/usr/include/python2.7/pgen.h"
export *
}
explicit module pgenheaders {
header "/usr/include/python2.7/pgenheaders.h"
export *
}
explicit module py_curses {
header "/usr/include/python2.7/py_curses.h"
export *
}
explicit module pygetopt {
header "/usr/include/python2.7/pygetopt.h"
export *
}
explicit module pythread {
header "/usr/include/python2.7/pythread.h"
export *
}
explicit module structmember {
header "/usr/include/python2.7/structmember.h"
export *
}
explicit module structseq {
header "/usr/include/python2.7/structseq.h"
export *
}
explicit module timefuncs {
header "/usr/include/python2.7/timefuncs.h"
export *
}
explicit module token {
header "/usr/include/python2.7/token.h"
export *
}
explicit module ucnhash {
header "/usr/include/python2.7/ucnhash.h"
export *
}
explicit module ncurses {
header "/usr/include/ncurses.h" // note: same as curses.h
export *
explicit module dll {
header "/usr/include/ncurses_dll.h"
export *
}
explicit module unctrl {
header "/usr/include/unctrl.h"
export *
}
}
// FIXME: true/false issues might be a compiler bug
exclude header "/usr/include/python2.7/pymactoolbox.h"
exclude header "/usr/include/python2.7/asdl.h"
exclude header "/usr/include/python2.7/ast.h"
exclude header "/usr/include/python2.7/Python-ast.h"
exclude header "/usr/include/python2.7/symtable.h"
// Note: missing #include here
exclude header "/usr/include/python2.7/pyexpat.h"
}
import sys
from ctypes import *
# NOTE: fn_name is copied from the output of nm (see INSTRUCTIONS.md)
if sys.platform == "darwin":
lib_ext = "dylib"
fn_name = "_TF3sit15mystringdoublerFGVSs20UnsafeMutablePointerVSC7_object_GS0_S1__"
else:
lib_ext = "so"
fn_name = "_TF3sit15mystringdoublerFGSpVSC7_object_GSpS0__"
########################################
# Calling the Simple way
########################################
libsit = PyDLL("./libsit.{0}".format(lib_ext))
prototype = PYFUNCTYPE(
py_object, # return type
py_object, # args type, arg type, arg type
)
str_doubler = prototype((fn_name, libsit))
print str_doubler("Hamburger")
########################################
# Calling the Complicated Way…
########################################
def swift_fn(name, ret_type, *arg_types):
prototype = PYFUNCTYPE(
ret_type,
*arg_types
)
return lambda self, *args: prototype((name, self.lib))(*args)
class SwiftLib(object):
def __init__(self):
self.lib = PyDLL(self.lib_file)
class SwiftInteropTest(SwiftLib):
lib_file = "./libsit.{0}".format(lib_ext)
double_str = swift_fn(
fn_name,
py_object,
py_object,
)
lib = SwiftInteropTest()
print lib.double_str("Bang!")
import Python
// Some stuff to make writing Swift code easier on you :)
public typealias PythonObject = UnsafeMutablePointer<Python.PyObject>
extension String {
func toPyString() -> PythonObject {
let len = Array(self.utf8).count
return PyString_FromStringAndSize(self, len)
}
static func fromPyString(pyString: PythonObject) -> String? {
let cstring = PyString_AsString(pyString)
return String.fromCString(cstring)
}
}
// Actual function for use in python:
public func mystringdoubler(inPyStr: PythonObject) -> PythonObject {
let instr = String.fromPyString(inPyStr)!
return "\(instr) \(instr)".toPyString()
}
@jiaaro

This comment has been minimized.

Copy link
Owner Author

jiaaro commented Apr 7, 2017

Related links:

Googling for "swiftc -emit-library" and it's variants was pretty productive as well

@frogcjn

This comment has been minimized.

Copy link

frogcjn commented Jan 23, 2018

How to use Python 3 with Swift?

@alexbell1

This comment has been minimized.

Copy link

alexbell1 commented Mar 2, 2018

this also apply for macOS?

@joekiller

This comment has been minimized.

Copy link

joekiller commented Oct 23, 2018

Using @_cdecl("mystringdoubler") on public func mystringdoubler(inPyStr: PythonObject) will allow you to export the symbol predictably. https://forums.swift.org/t/communicating-with-dynamically-loaded-swift-library/6769/3

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.