gpt4 book ai didi

python - 避免 Python 中的模块命名空间污染

转载 作者:行者123 更新时间:2023-12-04 16:50:41 25 4
gpt4 key购买 nike

TL;博士:将实现细节排除在模块命名空间之外的最简洁方法是什么?

已经有许多关于这个主题的类似问题,但相对于现代工具和语言特性,似乎没有一个有令人满意的答案。

我正在设计一个 Python 包,我想保持每个模块的公共(public)接口(interface)干净,只公开预期的内容,隐藏实现细节(尤其是导入)。

多年来,我看到了许多技术:

别担心。只需记录如何使用您的包,让其消费者忽略实现细节。

在我看来,这太可怕了。一个设计良好的界面应该很容易被发现。公开可见的实现细节会使界面更加困惑。即使作为包的作者,我也不想在它暴露太多时使用它,因为它会使自动完成功能变得不那么有用。

在所有实现细节的开头添加下划线。

这是一个很好理解的约定,大多数开发工具都足够智能,至少可以将下划线前缀的名称排序到自动完成列表的底部。如果你有少量的名字来这样处理它工作得很好,但是随着名字的数量增加,它变得越来越乏味和丑陋。

以这个相对简单的导入列表为例:

import struct

from abc import abstractmethod, ABC
from enum import Enum
from typing import BinaryIO, Dict, Iterator, List, Optional, Type, Union

应用下划线技术,这个相对较小的导入列表变成了这个怪物:

import struct as _struct

from abc import abstractmethod as _abstractmethod, ABC as _ABC
from enum import Enum as _Enum
from typing import (
BinaryIO as _BinaryIO,
Dict as _Dict,
Iterator as _Iterator,
List as _List,
Optional as _Optional,
Type as _Type,
Union as _Union
)

现在,我知道从不做 from 可以部分缓解这个问题。进口,只进口整个包,和包限定一切。虽然这确实有助于这种情况,而且我意识到有些人无论如何都喜欢这样做,但这并不能消除问题,这不是我的偏好。有些包我更喜欢直接导入,但我通常更喜欢显式导入类型名称和装饰器,以便我可以不加限定地使用它们。

下划线前缀还有一个小问题。参加以下公开类(class):

class Widget(_ABC):
@_abstractmethod
def implement_me(self, input: _List[int]) -> _Dict[str, object]:
...

这个包的消费者实现了他自己的 Widget执行会看到他需要执行 implement_me方法,需要取一个 _List并返回 _Dict .这些不是实际的类型名称,现在实现隐藏机制已经泄露到我的公共(public)接口(interface)中。这不是一个大问题,但它确实导致了这个解决方案的丑陋。

隐藏函数内部的实现细节。

这个绝对是hacky,它不能很好地与大多数开发工具一起使用。

下面是一个例子:

def module():
import struct

from abc import abstractmethod, ABC
from typing import BinaryIO, Dict, List

def fill_list(r: BinaryIO, count: int, lst: List[int]) -> None:
while count > 16:
lst.extend(struct.unpack("<16i", r.read(16 * 4)))
count -= 16
while count > 4:
lst.extend(struct.unpack("<4i", r.read(4 * 4)))
count -= 4
for _ in range(count):
lst.append(struct.unpack("<i", r.read(4))[0])

def parse_ints(r: BinaryIO) -> List[int]:
count = struct.unpack("<i", r.read(4))[0]
rtn: List[int] = []
fill_list(r, count, rtn)
return rtn

class Widget(ABC):
@abstractmethod
def implement_me(self, input: List[int]) -> Dict[str, object]:
...

return (parse_ints, Widget)

parse_ints, Widget = module()
del module

这有效,但它 super hacky,我不希望它在所有开发环境中都能正常运行。 ptpython例如,无法为 parse_ints 提供方法签名信息功能。还有 Widget的类型变成 my_package.module.<locals>.Widget而不是 my_package.Widget ,这让消费者感到奇怪和困惑。

使用 __all__ .

这是这个问题的常见解决方案:列出全局 __all__ 中的“公共(public)”成员。多变的:

import struct

from abc import abstractmethod, ABC
from typing import BinaryIO, Dict, List

__all__ = ["parse_ints", "Widget"]

def fill_list(r: BinaryIO, count: int, lst: List[int]) -> None:
... # You've seen this.

def parse_ints(r: BinaryIO) -> List[int]:
... # This, too.

class Widget(ABC):
... # And this.

这看起来不错很干净,但不幸的是,唯一的东西 __all__影响是使用通配符导入时会发生什么 from my_package import * ,无论如何,大多数人都不会这样做。

将模块转换为子包,并在 __init__.py中公开公共(public)接口(interface).

这就是我目前正在做的事情,在大多数情况下它很干净,但是如果我公开多个模块而不是展平所有内容,它会变得丑陋:
my_package/
+--__init__.py
+--_widget.py
+--shapes/
+--__init__.py
+--circle/
| +--__init__.py
| +--_circle.py
+--square/
| +--__init__.py
| +--_square.py
+--triangle/
+--__init__.py
+--_triangle.py

然后我的 __init__.py文件看起来像这样:

# my_package.__init__.py

from my_package._widget.py import parse_ints, Widget

# my_package.shapes.circle.__init__.py

from my_package.shapes.circle._circle.py import Circle, Sphere

# my_package.shapes.square.__init__.py

from my_package.shapes.square._square.py import Square, Cube

# my_package.shapes.triangle.__init__.py

from my_package.shapes.triangle._triangle.py import Triangle, Pyramid

这使我的界面干净,并且可以很好地与开发工具配合使用,但是如果我的包不是完全扁平的,它会使我的目录结构变得非常困惑。

有没有更好的技术?

最佳答案

转换为子包以限制一个地方的类数量并分离关注点。
如果在其模块之外不需要类或常量,请在其前面加上双下划线。如果您不想从中显式导入许多类,请导入模块名称。
你已经列出了所有的解决方案。

关于python - 避免 Python 中的模块命名空间污染,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57728884/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com