Python 闭包是“__all__”的良好替代品吗?

发布于 2024-08-08 22:09:16 字数 803 浏览 4 评论 0原文

使用闭包而不是 __all__ 来限制 Python 模块公开的名称是个好主意吗?这可以防止程序员意外地使用错误的模块名称(import urllib; urllib.os.getlogin()),并避免使用“from x import *”命名空间污染为__all__

def _init_module():
   global foo
   import bar
   def foo():
       return bar.baz.operation()
   class Quux(bar.baz.Splort): pass
_init_module(); del _init_module

与使用 __all__ 的相同模块相比:

__all__ = ['foo']
import bar
def foo():
    return bar.baz.operation()
class Quux(bar.baz.Splort): pass

函数可以采用这种风格来避免污染模块名称空间:

def foo():
    import bar
    bar.baz.operation()

这对于想要帮助用户区分其 API 和包对其 API 的使用的大型包可能会有所帮助。以及交互式内省期间其他模块的 API。另一方面,也许 IPython 应该在制表符完成期间简单地区分 __all__ 中的名称,并且更多用户应该使用允许他们在文件之间跳转以查看每个名称的定义的 IDE。

Is it a good idea to use a closure instead of __all__ to limit the names exposed by a Python module? This would prevent programmers from accidentally using the wrong name for a module (import urllib; urllib.os.getlogin()) as well as avoiding "from x import *" namespace pollution as __all__.

def _init_module():
   global foo
   import bar
   def foo():
       return bar.baz.operation()
   class Quux(bar.baz.Splort): pass
_init_module(); del _init_module

vs. the same module using __all__:

__all__ = ['foo']
import bar
def foo():
    return bar.baz.operation()
class Quux(bar.baz.Splort): pass

Functions could just adopt this style to avoid polluting the module namespace:

def foo():
    import bar
    bar.baz.operation()

This might be helpful for a large package that wants to help users distinguish its API from the package's use of its and other modules' API during interactive introspection. On the other hand, maybe IPython should simply distinguish names in __all__ during tab completion, and more users should use an IDE that allows them to jump between files to see the definition of each name.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

甚是思念 2024-08-15 22:09:16

我喜欢编写尽可能简单的代码。

__all__ 是 Python 的一项功能,显式添加是为了解决限制模块可见名称的问题。当你使用它时,人们立即明白你在用它做什么。

你的闭包技巧非常不标准,如果我遇到它,我不会立即理解它。您需要添加一个长注释来解释它,然后您需要添加另一个长注释来解释为什么这样做而不是使用 __all__

编辑:现在我更好地理解了这个问题,这是一个替代答案。

在 Python 中,在模块中使用下划线作为私有名称前缀被认为是一种很好的做法。如果您执行 from the_module_name import * 您将获得所有不以下划线开头的名称。因此,我更希望看到首字母下划线习惯用法的正确使用,而不是闭包技巧。

请注意,如果您使用以下划线开头的名称,则甚至不需要使用 __all__

I am a fan of writing code that is absolutely as brain-dead simple as it can be.

__all__ is a feature of Python, added explicitly to solve the problem of limiting what names are made visible by a module. When you use it, people immediately understand what you are doing with it.

Your closure trick is very nonstandard, and if I encountered it, I would not immediately understand it. You would need to put in a long comment to explain it, and then you would need to put in another long comment to explain why you did it that way instead of using __all__.

EDIT: Now that I understand the problem a little better, here is an alternate answer.

In Python it is considered good practice to prefix private names with an underscore in a module. If you do from the_module_name import * you will get all the names that do not start with an underscore. So, rather than the closure trick, I would prefer to see correct use of the initial-underscore idiom.

Note that if you use the initial underscore names, you don't even need to use __all__.

暮光沉寂 2024-08-15 22:09:16

from x import * 的问题在于它可以隐藏 NameErrors,这使得微不足道的错误难以追踪。 “命名空间污染”意味着向命名空间添加你不知道它来自哪里的东西。

这也是你的闭包所做的事情。另外,它可能会混淆 IDE、outline、pylint 等。

使用模块的“错误”名称也不是真正的问题。无论您在何处导入模块对象,它们都是相同的。如果“错误”的名称消失(更新后),则应明确原因并激励程序员下次正确执行此操作。但它不会导致错误。

The problem with from x import * is that it can hide NameErrors which makes trivial bugs hard to track down. "namespace pollution" means adding stuff to the namespace that you have no idea where it came from.

Which is kind of what your closure does too. Plus it might confuse IDEs, outlines, pylint and the like.

Using the "wrong" name for a module is not a real problem either. Module objects are the same from wherever you import them. If the "wrong" name disappears (after a update) it should be clear why and motivate the programmer to do it properly next time. But it doesn't cause bugs.

仙女山的月亮 2024-08-15 22:09:16

好吧,我开始对这个问题有了更多的了解。关闭确实允许隐藏私人物品。这是一个简单的例子。

没有闭包:

# module named "foo.py"
def _bar():
    return 5

def foo():
    return _bar() - 2 

有闭包:

# module named "fooclosure.py"
def _init_module():
    global foo
    def _bar():
        return 5

    def foo():
        return _bar() - 2

_init_module(); del _init_module

使用示例:

>>> import foo
>>> dir(foo)
['__builtins__', '__doc__', '__file__', '__name__', '__package__', '_bar', 'foo']
>>>
>>> import fooclosure
>>> dir(fooclosure)
['__builtins__', '__doc__', '__file__', '__name__', '__package__', 'foo']
>>>

这实际上是令人不安的微妙。在第一种情况下,函数 foo() 仅具有对名称 _bar() 的引用,如果您要删除 _bar()从名称空间来看,foo() 将停止工作。 foo() 每次运行时都会查找 _bar()

相比之下,foo() 的闭包版本可以在命名空间中不存在 _bar() 的情况下工作。我什至不确定它是如何工作的...它是否持有对为 _bar() 创建的函数对象的引用,或者它是否持有对名称的引用空间仍然存在,以便它可以查找名称 _bar() 并找到它?

Okay, I'm beginning to understand this issue a bit more. The closure really does allow for hiding private stuff. Here's a simple example.

Without the closure:

# module named "foo.py"
def _bar():
    return 5

def foo():
    return _bar() - 2 

With the closure:

# module named "fooclosure.py"
def _init_module():
    global foo
    def _bar():
        return 5

    def foo():
        return _bar() - 2

_init_module(); del _init_module

Sample of usage:

>>> import foo
>>> dir(foo)
['__builtins__', '__doc__', '__file__', '__name__', '__package__', '_bar', 'foo']
>>>
>>> import fooclosure
>>> dir(fooclosure)
['__builtins__', '__doc__', '__file__', '__name__', '__package__', 'foo']
>>>

This is actually disturbingly subtle. In the first case, function foo() simply has a reference to the name _bar(), and if you were to remove _bar() from the name space, foo() would stop working. foo() looks up _bar() each and every time it runs.

In contrast, the closure version of foo() works without _bar() existing in the name space. I'm not even certain how it works... is it holding a reference to the function object created for _bar(), or is it holding a reference to a name space that still exists, such that it can look up the name _bar() and find it?

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文