【问题标题】:Is it possible to make subpackage appear as the actual package是否可以使子包显示为实际包
【发布时间】:2017-11-17 22:54:16
【问题描述】:

我来到this question,并对tensorflow 开发人员试图使tensorflow 目录显示为实际包而感到非常失望,而实际包根实际上是tensorflow/python。通过使用__init__.py 格式的文件

from tensorflow.python import *
del python

他们努力实现这一目标。这会导致在使用包时出现一些不一致的行为(至少在我看来是这样),例如

import tensorflow.python                # seems to work
tensorflow.python                       # AttributeError: no attribute 'python'
from tensorflow.python import Session   # works as expected
tensorflow.python.Session               # AttributeError: no attribute 'python'
from tensorflow import python           # works as expected

tensorflow.nn                           # works as expected
import tensorflow.nn                    # ImportError: no module 'tensorflow.nn'
tensorflow.nn.tanh                      # works as expected
from tensorflow.nn import tanh          # ImportError: no module 'tensorflow.nn'

现在,我想知道是否/如何可以避免大多数/所有这些问题以获得更一致的行为。通过不删除python 属性,可以轻松解决第一组不一致问题。但是,考虑到目标是让整个包看起来像是一个子包,这可能并不完全令人满意。

为简单起见,让我们考虑以下包结构

package/
    __init__.py
    api/
        __init__.py
        code.py

package/api/code.py 看起来像

def a(): 
    return 'alpha'
def b(): 
    return 'bravo'

package/api/__init__.py 将是

import package.api.code

是否可以创建package/__init__.py 以便以下工作

import package.api                      # ImportError: no module 'package.api'
package.api                             # AttributeError: no attribute 'api'
from package.api import code            # ImportError: no module 'package.api'
package.api.code                        # AttributeError: no attribute 'api'
from package import api                 # ImportError: cannot import 'api'

package.code                            # works as expected
import package.code                     # works as above
package.code.a                          # works as expected
from package import a                   # correctly imports function a

我相信最后四行代码应该通过添加到sys.modules 来给出预期的结果,但我似乎无法找到使import package.api 失败的方法。

有人知道如何做到这一点吗?请随意指出我忽略或应考虑实现上述目标的用例。

【问题讨论】:

  • 可以试试:import package.code as code
  • @PeterMajko 你是什么意思?如果import package.code 正常工作,这应该正常工作......
  • 如果你使用 "import x from y as z" 可以完美运行:Check this image
  • @PeterMajko 我知道,我注意到from tensorflow import python 确实有效(我相应地编辑了我的问题),但应该吗?我假设tensorflow/__init__.py 中的del python 的想法是让tensorflow 包看起来好像是tensorflow/python。在这种情况下,这不是我希望看到的那种行为。
  • 他们这样做是有原因的。也许问他们为什么采取这种方法? :)

标签: python packages python-import


【解决方案1】:

首先 - 我必须说我真的讨厌这样的“阴暗”技术。它对各种 IDE 智能感知有不良影响,并使库结构难以理解。不过还是……

如果你想让子模块 code.py 作为一个实际的子包,你需要创建一个虚拟模块:

package/
    __init__.py
    api/
        __init__.py
        code.py
    code/
        __init__.py

在代码/__init__py 中添加这个:

from package.api.code import *

这个在 package/__init__.py:

from package.code import *

然后这部分应该按预期工作:

import package.code            # works as expected
package.code                   # works as expected
package.code.a                 # works as expected
from package import a          # works as expected

如果您进一步将其添加到包/__init__.py:

import package.api
del package.api

您基本上断开了用户访问 package.api 的连接,但没有别的,他们仍然可以使用 'from x import y' 通过子包访问子模块:

import package.api            # works
package.api.a()               # AttributeError: module 'package' has no attribute 'api'

import package.api.code       # works
package.api.code.a()          # AttributeError: module 'package' has no attribute 'api'

from package.api import code  # works
code.a()                      # works

from package import api       # works
api.code.a()                  # AttributeError: module 'package.api' has no attribute 'code'

【讨论】:

  • 我实际上希望看到一致的行为:如果导入 import package.api 有效,package.api.a() 也应该有效。但是,让import package.api 失败并返回ImportError 并在调用package.api.a() 时保留AttributeError 会更有趣。
  • 另一件事是 - 你真的不应该这样做,如果你只想让它“好”并将子模块作为子包推广 -.- 我会为此杀人
  • 在不调整导入系统的情况下使导入一致(因此“很好”)是相当容易的(只需摆脱删除语句,一切都是一致的)。我只是想知道 python 在这个主题上的可能性/限制是什么。请不要杀我...
  • 大声笑 :) 没关系。对于有关导入/加载的所有可能性,我建议this。我相信您将能够根据所有这些知识构建出您所描述的有效的东西-问题是-您为什么要这样做? :)(不用回答,哈哈)
【解决方案2】:

我设法写了一些几乎有效的东西(package/__init__.py):

import sys

from package.api import *

for key in sys.modules:
    parts = key.split('.')
    if len(parts) > 1 and parts.pop(0) == __name__:
        subkey = parts.pop(0)
        if subkey == 'api' and len(parts) == 0:
            sys.modules['.'.join([__name__, subkey])] = None
        elif subkey == 'api':
            m = sys.modules.pop(key)
            sys.modules['.'.join([__name__] + parts)] = m
del api        
del sys

导入错误表明它仍然是一个 hack,但除此之外,大多数示例都按指定的方式工作在运行我的问题中的语句之前已被调用)。如果第一个语句是import package.api,那么就没有我想要的ImportError

为了解决这个问题,我偶然发现了this answer,它实际上导致了相同的行为,但代码更优雅:

import sys

from package import api

# clean up this module
self = sys.modules.pop(__name__)
del self

# this module becomes hidden module
sys.modules[__name__] = api
sys.modules[api.__name__] = None

del api
del sys

但是,这仍然存在如果第一次导入类似于import package.api,则不会抛出ImportError 的问题。

【讨论】:

    猜你喜欢
    • 2018-02-07
    • 2023-01-01
    • 2021-10-24
    • 2014-11-03
    • 2017-05-29
    • 1970-01-01
    • 1970-01-01
    • 2012-06-14
    • 2017-05-01
    相关资源
    最近更新 更多