如何更直接地引用函数?

5

列出目录 analyse 的结构如下:

tree analyse
analyse
├── __init__.py
└── vix.py

__init__.py 是空白的,vix.py 包含一个名为 draw_vix 的函数。

import analyse
analyse.vix.draw_vix()

draw_vix 在代码中被引用为 analyse.vix.,我想改为使用 vix 而不是 analyse.vix,也就是在 import analyse 后加入 vix 命名空间。
编辑 __init__.py:

import analyse.vix as vix
from analyse import vix

请检查:

import analyse
vix.draw_vix()

错误信息:
NameError: name 'vix' is not defined

@hl037_,如果我在__init__.py中添加from .vix import vix

import analyse
vix.draw_vix()

它还存在另一个问题: ImportError: 无法导入名称 'vix'。 我想要的是在 __init__.py 中编写某些内容,或者在 vix.py 中编写某些内容,以使 vix.draw_vix() 生效。
如何解决?


我在我的回答中加入了一些例子。 - hl037_
@scrapy,我添加了__import__内置函数的重载以确保这个技巧始终有效。 - Pak Uula
from analyse import viximport analyse 有什么问题? - leoschet
2个回答

5

你就是不能喜欢这个。

import analyse

只会将analyze放入您的全局命名空间中。 最好的方法是:

from analyse import vix

将vix放入全局变量中。
(或使用星号表示法)
奖励:如果您不想安装需要分析(即使使用venv,实际上建议在venv内部安装应用程序/模块),并且要在__init__.py中导入vix,则可以在以下位置进行相对导入:
from . import vix

https://docs.python.org/zh-cn/3/reference/import.html

[编辑] 一些示例:

文件系统:

analyse
 ├─ __init__.py
 ├─ vix.py
 ├─ viz.py # another submodule to demonstrate some other points
 └─ vit.py # yet another submodule to demonstrate some other points

analyse/__init__.py 包含:

import .vit as vit
# from . import vit # works too
vix = 42
viy = 43

analyse/vix.py包含以下内容:

def draw_vix():
  print('Hello from drax_vix')

analyse/viz.py 包含:

def draw_viz():
  print('Hello from drax_viz')

analyse/vit.py 包含以下内容:

def draw_vit():
  print('Hello from drax_vit')

如果您的软件包已经安装(例如,您创建了 setup.py 并使用 pip install analyse 进行安装),或作为 PYTHONPATH 环境变量中列出的目录的子目录,在任何脚本(或 Python 解释器)之外调用包时,会发生以下情况:

1)使用别名的完全导入:

import analyse.vix as vix
vix.draw_vix()
# works, and print print('Hello from drax_vix')
analyse
# Error : analyse not defined since you import only symbols from analyse.vix, not the modules themselves
analyse.vix
# Same as previous one

2) 完整导入

import analyse.vix
analyse.vix.draw_vix()
#Works because python guarantees analyse.vix to be a valid expression
analyse
# analyse module
vix.draw_vix()
# Error : vix undefined. import analyse.vix guarantee analyse.vix to be a valid module, but does not add submodule to global namespace
analyse.vix
# is a module, analyse.vix module has been imported too and set as an attribute of analyse.
analyse.viz
# Error : analyse has no attr viz since viz is a non-imported submodule.
analyse.vit.draw_vit
# Works because analyse is indirectly imported with import analyse.vix, and vit is imported as vit inside analyse/__init__.py and thus an attribute of the module.

3) 仅导入分析

import analyse
analyse.vix
# 42 because analyse.vix module is not imported, thus you saw the variable defined vix inside analyse
vix.draw_vix()
# Error : vix undefined, obviously, you never defined it nor imported it
vit.draw_vit()
# Works because vit is imported in __init__.py and set as an attribute of analyse.

4) 从analyse模块中导入vix、viy、viz和vit。

from analyse import vix, viz, vit
analyse.vix
# Error : analyse not define since you imported only symbols
vix
# 42 Because you didn't import analyse.vix and there is a variable vix defined in analyse/__init__.py
viy
# 43
viz
# module viz : viz is not declared as an attribute of analyse, but it falls back to the existing analyse.viz module
vit
# module vit : The one imported inside __init__.py (that is the same as analyse.vit)

5) 从analyse模块导入viy

from analyse import viy
# Error : analyse/viy.py does not exist

__init__.py中写入from analyse import vix没有任何用处。 - user7988893
哦,我以为他在 analyse.vix 模块内定义了一个 vix 符号。 - hl037_
仅限于分析模块内部。 - hl037_
from . import vix__init__.py 中也无法工作。 - user7988893

4

在导入分析后将vix添加到命名空间中

默认情况下,模块中不可见顶层作用域。模块中的globals函数返回模块级别的作用域,而不是导入模块的作用域(参见docs)。因此,需要一些技巧将vix放到外部作用域中。

第一个技巧。

Python中的import语句实际上是一个函数调用,并且来自__init__.py的代码在某个堆栈帧内执行。技巧是找到对应于导入模块的堆栈帧并污染其作用域。虽然这是可能的,但这是一种非常糟糕的做法。它违反了Python的范例,即简单的事情必须是简单的。如果您需要在顶层作用域中使用vix,请使用import analyse.vix as vix。作用域污染可能会导致名称冲突和非常微妙的错误。

第二个技巧。

__init__.py中的代码仅在包的第一次加载时执行。在所有其他import analyse语句中,Python解释器使用缓存在sys.modules中的模块对象。我找到的唯一方法是拦截对__import__内置函数的调用并添加作用域污染钩子。

我强烈建议避免使用这种“魔法”。不过,如果你想自己给自己挖坑,那就来吧。

将以下代码放入analyse/__init__.py中:

from . import vix # create module "vix" in the current scope

import inspect as _inspect

# Inject `vix` at the first import
_frame = _inspect.currentframe().f_back
while _frame is not None:
    # skip Pyhton module loader
    if "importlib._bootstrap" in _frame.f_code.co_filename:
        _frame = _frame.f_back
        continue
    #this frame corresponds to the scope, where `import analyse` is specified
    _frame.f_globals["vix"] = vix # bind the name 'vix' with the module 'vix' from the current scope
    print("DEBUG: injected vix to ", _frame)
    break
# remove _frame from the scope
del[_frame]

# overload __import__ function to inject `vix` on every succeeding import of `analyse`
import builtins as _builtins
# save builtin that `import` calls
_builtin_import = _builtins.__import__ 

def _never_overload_import(name, globals=None, locals=None, fromlist=(), level=0):
    "This function injects `vix` in every scope that imports `analyse` module"
    global _builtin_import
    global vix
    result = _builtin_import(name, globals, locals, fromlist, level)
    if name == "analyse" and globals is not None and not "vix" in globals:
        globals["vix"] = vix
        print("DEBUG: injected vix to ", _inspect.currentframe().f_back)
    return result

_builtins.__import__ = _never_overload_import

这是我的测试方式:
  1. 文件secondary.py只加载目标模块。
import analyse
# demonstrate that first import created `vix` at this scope
vix.draw_vix("called from secondary as `vix.draw_vix`")
  1. 文件 test.py 加载了 secondary.pyanalyse
print("***Loading secondary***")
import secondary
print("***Secondary loaded***")

print("***Loading analyse***")
import analyse
print("***Analyse loaded***")

analyse.vix.draw_vix("called as `analyse.vix.draw_vix`")
vix.draw_vix("called as `vix.draw_vix`")

输出:
***Loading secondary***
DEBUG: injected vix to  <frame at 0x00909AE8, file 'secondary.py', line 1, code <module>>
draw vix,  called from secondary as `vix.draw_vix`
***Secondary loaded***
***Loading analyse***
DEBUG: injected vix to  <frame at 0x00911C30, file 'test.py', line 6, code <module>>
***Analyse loaded***
draw vix,  called as `analyse.vix.draw_vix`
draw vix,  called as `vix.draw_vix`

更新日志

添加了重载__import__函数的功能。原始答案只是将vix对象插入到导入作用域的全局变量中,但这并不足够:在__init__.py中的代码仅在第一次加载包时执行一次。当包在另一个文件中被导入时,它将看不到vix模块,因为注入代码未被执行。


1
如果模块已经被导入,这个技巧就不起作用了,因为 traceback hack 不会再次被调用,由于 Python 默认不重新加载模块。只有第一个导入它的模块才会在全局范围内获得 vix。 - hl037_
@hl037_,感谢你的点赞。我更新了答案:重载__import__内置函数,将vix注入到每个加载目标模块的作用域中。 - Pak Uula

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接