如何在Objective-C OS X应用中嵌入Python用于插件?
我正在尝试在一个新的OS X应用程序中使用Python来做插件脚本。我想把一些程序逻辑转移到Python上,这样可以方便地进行实时修改。我开始时参考了Big Nerd Ranch的教程。这个教程看起来有效,但它建议使用一种较旧的Python链接方法。自从Xcode 5以来,我们应该按照这个苹果开发者文档来安装Python。然而,这个过程似乎创建了一个链接的Python实例,而不是嵌入式的。我尝试按照这个问题的回答中的指导进行操作,但似乎没有成功。
所以我的问题是:现在在Objective C的Mac OS X应用中,使用Python作为插件运行时的最佳实践是什么?如何确保Python和应用程序打包在一起,并且在最终构建Objective C应用之前,可以安装任何额外的库?
1 个回答
我想出了一个看起来还不错的方法。
首先,从官方网站下载你想用的Python版本的源代码。把下载的文件解压到某个地方。我使用的是Python 3.4.2。根据你自己系统上的具体版本调整命令。
创建一个用于这个开发版Python的构建目录。整个目录名中不要有空格,这样bash才能正确识别开头的(#!)行。我用的是/Users/myaccount/development/python/devbuild/python3.4.2
。
进入解压后的Python目录,运行以下命令:
./configure --prefix="/Users/myaccount/development/python/devbuild/python3.4.2"
make
make install
这会在你指定的开发构建目录中安装Python。接下来设置Python的路径,确保使用正确的目录:
export PYTHONPATH="/Users/myaccount/development/python/devbuild/python3.4.2/lib/python3.4/site-packages/"
进入Python的bin目录(/Users/myaccount/development/python/devbuild/python3.4.2/bin
),用那里的pip3
来安装你需要的模块。$PYTHONPATH
设置会确保模块安装到正确的site-packages
目录。
找个方便的地方放PyObjC的代码库,并把它克隆到那里。然后检查最新的版本标签并安装,确保你的$PYTHONPATH
仍然是正确的:
hg clone https://bitbucket.org/ronaldoussoren/pyobjc
cd pyobjc
hg tags
hg checkout [the id of the latest version from the tag list]
/Users/myaccount/development/python/devbuild/python3.4.2/bin/python3 ./install.py
每当你需要更新Python模块时,只需确保使用正确的Python bin和$PYTHONPATH
。
现在来把Python添加到Xcode项目中。
把/Users/myaccount/development/python/devbuild/python3.4.2
目录拖到Xcode项目中,设置为不复制必要的项目,并创建一个文件夹引用。
在Xcode项目的Build Settings
中,把/Users/myaccount/development/python/devbuild/python3.4.2/include/python3.4m
添加到Header Search Paths
设置中。不确定是否有办法将这个步骤通用化,只是搜索我们刚刚添加的目录。
把/Users/myaccount/development/python/devbuild/python3.4.2/lib/libpython3.4m.a
库拖到Xcode项目中,同样设置为引用而不复制。
现在可以使用来自Big Nerd Ranch脚本教程库的代码,只需做一些修改。
插件管理器的代码需要一个NSString扩展,以便与Python API喜欢的wchar_t
字符串一起工作:
@interface NSString (WcharEncodedString)
- (wchar_t*) getWideString;
@end
@implementation NSString (WcharEncodedString)
- (wchar_t*) getWideString {
const char* tmp = [self cStringUsingEncoding:NSUTF8StringEncoding];
unsigned long buflen = strlen(tmp) + 1;
wchar_t* buffer = malloc(buflen * sizeof(wchar_t));
mbstowcs(buffer, tmp, buflen);
return buffer;
}
@end
Python的头文件应该这样包含:
#include "Python.h"
在调用Py_Initialize()
之前,需要运行以下代码,以设置正确的Python可执行文件、PYTHONPATH和PYTHONHOME,正如Zorg在另一个问题中建议的那样。
NSString* executablePath = [[NSBundle mainBundle] pathForResource:@"python3.4" ofType:nil inDirectory:@"python3.4.2/bin"];
Py_SetProgramName([executablePath getWideString]);
NSString* pythonDirectory = [[NSBundle mainBundle] pathForResource:@"python3.4" ofType:nil inDirectory:@"python3.4.2/lib"];
Py_SetPath([pythonDirectory getWideString]);
Py_SetPythonHome([pythonDirectory getWideString]);
最后,需要在PluginExecutor.py
文件中扩展Python路径,以包括高层lib
路径的各种子目录。在Plugin Executor文件的顶部添加以下代码:
import sys
from os import walk
path = sys.path.copy()
for p in path:
for root,dirs,files in walk(p):
if p is not root:
sys.path.append(root)
如果有问题我会更新,但目前看来这个方法是可行的。