Skip to content

Instantly share code, notes, and snippets.

@jiaaro
Last active July 28, 2024 00:09
Show Gist options
  • Save jiaaro/e111f0f64d0cdb8aca38 to your computer and use it in GitHub Desktop.
Save jiaaro/e111f0f64d0cdb8aca38 to your computer and use it in GitHub Desktop.
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()
}
@gduvalsc
Copy link

gduvalsc commented May 4, 2020

Note: swift and swiftc are now available under Centos 8. To install them:

dnf install -y epel-release
dnf install swift-lang

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment