Python is ubiquitous. There are so many open-source modules out there that embedding an interpreter in your app may allow you to incorporate sophisticated features with very little new coding.
Embedding Python has never been trivial, but Apple’s obsession with security, and its shift from Intel to Apple Silicon, has added another level of complexity to an already cumbersome process.
There are two pieces of open source software out there that promise to make this easy: PythonKit
, and Python_Apple_Support
. I was at first unsuccessful when following the provided instructions, and it took me quite a lot of experimentation and trial and error to discover the correct procedure.
In this tutorial, I delineate the exact steps I took to successfully embedded the Python interpreter in a Swift app using Xcode.
Install the Python-Apple-Support
binaries in your Xcode project.
First download the pre-built binaries using the following links:
for macOS: https://github.com/beeware/Python-Apple-support/releases/download/3.11-b1/Python-3.11-macOS-support.b1.tar.gz
for iOS: https://github.com/beeware/Python-Apple-support/releases/download/3.11-b1/Python-3.11-iOS-support.b1.tar.gz
Untar the downloaded file
tar -xvf Python-3.11-macOS-support.b1.tar.gz
Navigate to the Python-3.11-macOS-support.b1
folder and drag python-stdlib
and Python.xcframework
folders into your project
Check Copy items if needed
before clicking Finish
.
Add the PythonKit
Package Dependency.
In the search field at the upper right, type in the PythonKit’s git repo address (https://github.com/pvieito/PythonKit.git), select PythonKit
, and click Add Package
.
Verify Setting for Python.xcframework
Click the root node of your project, and select your app’s target. In the General
pane, scroll down to Frameworks, Libraries, and Embedded Content
and ensure that Python.xcframework
is set to Do Not Embed
.
Add the SystemConfiguration.Framework
Make sure that it is also set Do Not Embed
.
Ensure that the python-stdlib
folder will be copied as a bundle resource.
Create a module map for the Python.xcframework
Hit ⌘N and create a new blank file
Name it module.modulemap
, and save it in Python.xcframework/macos_arm64_x86_64/Headers
And paste in the following.
module Python {
umbrella header "Python.h"
export *
link "Python"
}
Add a Run Script
phase to codesign the PythonKit
binaries
Paste in the flowing script which signs the embedded Python binaries. (Do not use the script provided by the author. It is incorrect.)
set -e
echo "Signing contents of $CODESIGNING_FOLDER_PATH/Contents/Resources/python-stdlib/lib-dynload as $EXPANDED_CODE_SIGN_IDENTITY_NAME ($EXPANDED_CODE_SIGN_IDENTITY)"
cd "$CODESIGNING_FOLDER_PATH/Contents/Resources/python-stdlib/lib-dynload"
/usr/bin/codesign —-force —-sign "$EXPANDED_CODE_SIGN_IDENTITY" --options runtime —-timestamp=none —-preserve-metadata=identifier,entitlements,flags —-generate-entitlement-der *.so
Uncheck Based on dependency analysis
.
The Python runtime should be initialized as early in your app as possible. Below is some sample code illustrating how Python is accessed.
//
// EmbeddedPythonApp.swift
// EmbeddedPython
//
// Created by Edward Janne on 1/8/23.
//
import SwiftUI
import Python
import PythonKit
class EmbeddedPython: ObservableObject {
init() {
// Initialize the Python runtime early in the app
if let stdLibPath = Bundle.main.path(forResource: "python-stdlib", ofType: nil),
let libDynloadPath = Bundle.main.path(forResource: "python-stdlib/lib-dynload", ofType: nil)
{
setenv("PYTHONHOME", stdLibPath, 1)
setenv("PYTHONPATH", "\\(stdLibPath):\\(libDynloadPath)", 1)
Py_Initialize()
}
}
// Simple example of loading Python modules into the runtime
// and accessing Python object members in Swift
func printPythonInfo() {
let sys = Python.import("sys")
print("Python Version: \\(sys.version_info.major).\\(sys.version_info.minor)")
print("Python Encoding: \\(sys.getdefaultencoding().upper())")
print("Python Path: \\(sys.path)")
_ = Python.import("math") // verifies `lib-dynload` is found and signed successfully
}
}
@main
struct EmbeddedPythonApp: App {
@StateObject var embeddedPython = EmbeddedPython()
var body: some Scene {
WindowGroup {
ContentView()
.onAppear {
embeddedPython.printPythonInfo()
}
}
}
}