如何在Objective-C OS X应用中嵌入Python用于插件?

12 投票
1 回答
3883 浏览
提问于 2025-04-30 16:40

我正在尝试在一个新的OS X应用程序中使用Python来做插件脚本。我想把一些程序逻辑转移到Python上,这样可以方便地进行实时修改。我开始时参考了Big Nerd Ranch的教程。这个教程看起来有效,但它建议使用一种较旧的Python链接方法。自从Xcode 5以来,我们应该按照这个苹果开发者文档来安装Python。然而,这个过程似乎创建了一个链接的Python实例,而不是嵌入式的。我尝试按照这个问题的回答中的指导进行操作,但似乎没有成功。

所以我的问题是:现在在Objective C的Mac OS X应用中,使用Python作为插件运行时的最佳实践是什么?如何确保Python和应用程序打包在一起,并且在最终构建Objective C应用之前,可以安装任何额外的库?

暂无标签

1 个回答

20

我想出了一个看起来还不错的方法。

首先,从官方网站下载你想用的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)

如果有问题我会更新,但目前看来这个方法是可行的。

撰写回答