我正在编写一个基于蟒蛇熊猫的数据处理包。对于具有功能风格的部分,我想使我的包的层次结构更平顺。目前,需要使用以下调用导入函数:
from package.module.submodule import my_function拟议的修改将使进口成为可能。
from package import my_function为了实现这一点,函数和其他对象将被导入到package/__init__.py中,以便它们可以在顶级名称空间中使用。大熊猫就是这样做的,例如__init__.py使进口成为可能。
from pandas import DataFrame实际上,DataFrame类是在pandas.core.frame中定义的。通常您必须像这样导入它:from pandas.core.frame import DataFrame,但是由于它是在顶级__init__.py中导入的,所以它在顶层可用。
将功能作为顶级导入提供:
package/__init__.py导入以避免创建循环引用。- [Searching for from+pandas+import](https://github.com/pandas-dev/pandas/search?p=5&q=from+pandas+import) It seems that pandas always avoids importing from the top level (except test scripts which do use from pandas import DataFrame). I don't know how to enforce this.
- Maybe this tool can be helpful: [pylint-forbidden-imports](https://pypi.org/project/pylint-forbidden-imports/),
- or rather [flake8-tidy-imports](https://github.com/adamchainz/flake8-tidy-imports) since we are using black and flake8 as a pre commit hook. flake8-tidy-imports makes it possible to define which imports are forbidden. It seems it applies to the whole package though, and not to a specific location in the package.相关问题
发布于 2022-04-02 02:40:08
我认为您所表达的关注是,“导入子模块”和“在模块导入过程中导入子模块”并不是一回事。例如,用ipython编写它:
from module.sub.file import func并从module包中编写这篇文章
from module.sub.file import func不要做同样的事情(即使他们看起来是一样的)。这是因为如果module已经启动了它的初始化;那么后续对子模块的调用将不会重新初始化module,module也不需要在调用它的子模块之前完成初始化。这与类继承的工作方式非常相似。
这意味着包从所有“子模块”中提取各种函数是完全有效的,而每个子模块都可以通过包本身显式地相互导入,而不会导致无限循环。这是故意的。例如,
module
__init__.py
from .sub1.file1 import func1
from .sub2.file2 import func2
sub1
__init__.py
file1.py
from module.sub2.file2 import func2
def func1(x):
return func2(x)+x
sub2
__init__.py
file2.py
def func2(x):
return x+1在这里,子模块sub1依赖于sub2。行from module.sub2.file2 import func2通常意味着
module/__init__.py并从命名空间sub2加载module/sub2/__init__.py并从命名空间file2加载module/sub2/file2.py并从命名空间func2加载但是在from module import func1的调用过程中,当我们到达file1.py of from module.sub2.file2 import func2中的线路时,我们要么已经运行,要么正在运行module/__init__.py和module/sub/__init__.py。这意味着这一行更像是:
module/__init__.py目前是executed...skipmodule/sub2/__init__.py已经是loaded...skip了module/sub2/file2.py并从命名空间func2加载通常,如果当前正在执行module/__init__.py,则只需跳过对该语句的额外调用。您完全可以直接跳过import module本身,即使它还没有完成加载。添加一些打印语句
module
__init__.py
print('start init module')
from .sub1.file1 import func1
from .sub2.file2 import func2
print('end init module')
sub1
__init__.py
file1.py
print('loading module from file1.py')
import module
print('done loading module from file1.py')
from module.sub2.file2 import func2
def func1(x):
return func2(x)+x
sub2
__init__.py
file2.py
def func2(x):
return x+1现在运行from module import func1
start init module
loading module from file1.py
# Notice that nothing is printed here, meaning module/__init__.py was not run again,
# even though we explicitly wrote "import module", additionally "module" wasn't
# even finished executing it's own __init__.py file.
done loading module from file1.py
end init module从设计的角度来看,这是很棒的。这意味着sub2很可能是一个完全独立于module但依赖于module的包。然后,在某个时候,有人会说,“让我们把这个附属包作为子模块丢到我们的module包中”。整个文件夹被放入其中(不改变任何代码),然后module就可以导入它,就像它是本地的子包一样,而不需要意外地创建导入循环,即使子包依赖于模块本身的其他部分。
发布于 2022-04-05 04:26:53
发布于 2022-03-29 21:51:11
我已经搜索过熊猫GitHub存储库,无法找到解决您特定问题的预提交钩子,所以我从熊猫储存库中调整了堆芯阵列钩子。
我的测试设置具有以下文件夹结构(不包括.git文件夹):
├── package
│ ├── __init__.py
│ ├── module
│ │ └── submodule.py
│ └── second_module
│ └── submodule.py
├── .pre-commit-config.yaml
└── scripts
└── import_from_submodules.py.pre-commit-config.yaml包含
repos:
- repo: local
hooks:
- id: import-from-submodules
name: Import from appropriate submodules
language: python
entry: python scripts/import_from_submodules.py package
files: ^package/
types: [python]并且import_from_submodules.py文件包含
"""
Check that all imports reference the correct submodule and not import directly
from __init__.py, even though that is technically possible.
This is meant to be run as a pre-commit hook - to run it manually, you can do:
pre-commit run import-from-submodules --all-files
"""
from __future__ import annotations
import argparse
import ast
import sys
from typing import Sequence
class Visitor(ast.NodeVisitor):
def __init__(self, package_name: str, path: str) -> None:
self.package_name = package_name
self.path = path
self.error_message = (
"{path}:{lineno}:{col_offset}: "
f"Don't import from {self.package_name}, "
f"import from {self.package_name}.submodule instead\n"
)
def visit_Import(self, node: ast.Import) -> None:
if any(module.name == self.package_name for module in node.names):
msg = self.error_message.format(
path=self.path, lineno=node.lineno, col_offset=node.col_offset
)
sys.stdout.write(msg)
sys.exit(1)
super().generic_visit(node)
def visit_ImportFrom(self, node: ast.ImportFrom) -> None:
if node.module == self.package_name:
msg = self.error_message.format(
path=self.path, lineno=node.lineno, col_offset=node.col_offset
)
sys.stdout.write(msg)
sys.exit(1)
super().generic_visit(node)
def import_from_submodules(package_name: str, content: str, path: str) -> None:
tree = ast.parse(content)
visitor = Visitor(package_name, path)
visitor.visit(tree)
def main(argv: Sequence[str] | None = None) -> None:
parser = argparse.ArgumentParser()
parser.add_argument("package_name")
parser.add_argument("paths", nargs="*")
args = parser.parse_args(argv)
for path in args.paths:
with open(path, encoding="utf-8") as fd:
content = fd.read()
import_from_submodules(args.package_name, content, path)
if __name__ == "__main__":
main()这使用ast模块解析package目录中每个Python文件的python源代码,并访问每个import <module>和from <module> import <function>语句。如果<module>部件等于包名(这是您可以在预提交配置中设置的脚本的命令行参数),则打印违规行的位置,脚本退出时使用非零退出代码来指示存在错误。
假设package/module/submodule.py中有一个函数package/module/submodule.py,它也是在__init__.py中导入并包含在__all__中的。在package/second_module/submodule.py内部,如果运行pre-commit run import-from-submodules --all-files,下面的行将引发错误
import package
from package import fun
from ..package import fun鉴于
from package.module.submodule import fun
from ..module.submodule import fun别说了。请注意,相对导入示例说明,在将模块名与给定包名进行比较时,将忽略相对导入的所有前导点。
我希望这涵盖了你的用例。当然,欢迎您将错误消息更改为更有用/更清晰的信息。如果您想扩展这段代码,ast模块是非常强大的。例如,前面提到的堆芯阵列预提交钩子还通过检查每个属性访问来标记所有表达式pd.array。
https://stackoverflow.com/questions/71630256
复制相似问题