如何保护我的Python代码库,使访客无法看到某些模块但仍然能正常工作?

3 投票
3 回答
750 浏览
提问于 2025-04-15 14:24

我们正在用Python开始一个新项目,这个项目里有一些专有的算法和敏感的逻辑,我们希望这些内容能保持私密。同时,我们也会有一些外部人员(公众中的特定成员)参与代码的工作。我们不能让外部人员接触到那些小的私密代码,但我们希望能有一个公共版本,能让他们正常使用。

假设我们的项目叫Foo,它有一个模块bar,里面有一个函数get_sauce()。实际上,get_sauce()里发生的事情是保密的,但我们希望公共版本的get_sauce()能返回一个可以接受的、虽然不完全正确的结果。

我们还自己运行一个Subversion服务器,这样我们可以完全控制谁能访问什么。

符号链接

我最初的想法是使用符号链接——也就是说,给大家提供bar_public.py,而bar_private.py只给内部开发者。不幸的是,创建符号链接是一项繁琐的手动工作,尤其是当我们有大约二十个这样的私密模块时。

更重要的是,这样会让管理Subversion的权限文件变得困难,因为对于每个我们想保护的模块,服务器上都必须添加一个例外。有人可能会忘记这样做,结果不小心把秘密代码提交上去……那样的话,模块就会在代码库里,我们得重新构建代码库,希望外部人员在此期间没有下载到它。

多个代码库

private
└── trunk/
    ├── __init__.py
    └── foo/
        ├── __init__.py
        └── bar.py
public
└── trunk/
    ├── __init__.py
    └── foo/
        ├── __init__.py
        ├── bar.py
        ├── baz.py
        └── quux.py

这个想法是,只有内部开发者能够同时检出private/public/。内部开发者会设置他们的PYTHONPATH=private/trunk:public/trunk,而其他人只需设置PYTHONPATH=public/trunk。这样,内部人员和外部人员都可以from foo import bar,得到正确的模块,对吧?

让我们试试这个方法:

% PYTHONPATH=private/trunk:public/trunk python
Python 2.5.1
Type "help", "copyright", "credits" or "license" for more information.
>>> import foo.bar
>>> foo.bar.sauce()
'a private bar'
>>> import foo.quux
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ImportError: No module named quux

我不是Python专家,但似乎Python已经决定了模块foo的搜索方式,并且是相对的:

>>> foo
<module 'foo' from '/path/to/private/trunk/foo/__init__.py'>

甚至删除foo也没有帮助:

>>> import sys
>>> del foo
>>> del sys.modules['foo']
>>> import foo.quux
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ImportError: No module named quux

你能给我提供一个更好的解决方案或建议吗?

3 个回答

0

我在阅读Flask的文档时,发现了一个替代的解决方案:

flaskext/__init__.py

这个文件的唯一作用是将这个包标记为命名空间包。这样做是为了让来自不同PyPI包的多个模块可以放在同一个Python包里:

__import__('pkg_resources').declare_namespace(__name__)

如果你想确切了解这里发生了什么,可以查看distribute或setuptools的文档,它们会解释这个是怎么工作的。

2

使用某种插件系统,把你的插件保留给自己,同时也可以有一些公开的插件,和开源代码一起发布。

插件系统有很多种。你可以很简单地自己做一个。如果你想要更高级一点的,我推荐使用Zope组件架构,不过还有其他选择,比如setuptools的entry_points等等。

选择哪种插件系统适合你,这个问题可以作为第二个问题来考虑。

3

foo 这个包的 __init__ 方法里,你可以修改 __path__,让它去其他目录找模块。

所以,先创建一个叫 secret 的目录,把它放到你的私人 Subversion 仓库里。在 secret 里放上你的专有文件 bar.py。然后在公共的 foo 包的 __init__.py 文件里加上类似下面的内容:

__path__.insert(0,'secret')

这样一来,对于那些有私人仓库的用户来说,他们会在 foo 中找到 bar.py,因为 secret 是搜索路径中的第一个目录。对于其他用户来说,Python 找不到 secret,就会去 __path__ 中的下一个目录,加载正常的 bar.py 文件。

所以它的结构大概是这样的:

   private
    └── trunk/
        └── secret/
            └── bar.py
    public
    └── trunk/
        ├── __init__.py
        └── foo/
            ├── __init__.py
            ├── bar.py
            ├── baz.py
            └── quux.py

撰写回答