【问题标题】:Is a Python closure a good replacement for `__all__`?Python 闭包是 __all__ 的一个很好的替代品吗?
【发布时间】:2009-10-27 18:21:19
【问题描述】:

使用闭包而不是 __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 的使用区分开来的大型包很有帮助。另一方面,也许 IPython 应该在制表符补全期间简单区分 __all__ 中的名称,并且更多的用户应该使用允许他们在文件之间跳转以查看每个名称的定义的 IDE。

【问题讨论】:

  • 为什么不在使用这个模块的代码中使用from bar import foo?我觉得我错过了什么......
  • 第二位代码不使用第一位代码。它使用__all__ 定义相同的模块。
  • 我同意 jdb:这有多大问题?您的 _init_module 方法很奇怪,我不明白您为什么要打扰?例如,有多少开发人员不小心使用了 urllib.os.getlogin?只需编写代码并解决实际问题。
  • 我不明白你对闭包优点的解释。闭包如何“防止程序员意外使用错误的模块名称”?如果有人使用import foo as bar,那么他们会得到名字bar,如果他们使用import foo,他们就会得到名字foo。关闭有什么不同?
  • 我只有在 Plone 上工作时遇到过这个问题,因为它有太多的代码,没有人熟悉它,尤其是集成商。在不知不觉中,足够多的集成商通过自省发现特定名称,如果您决定删除例如from zope.app.intid.interfaces import IIntIds 来自您的模块,那么您将破坏他们的模块。当从 IPython shell 内省 API 时,很难知道任何特定的模块级名称应该是接口还是实现。

标签: python closures


【解决方案1】:

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

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

你的闭包技巧很不标准,如果我遇到它,我不会立即理解它。你需要写一个长评论来解释它,然后你需要再写一个长评论来解释你为什么这样做而不是使用__all__

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

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

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

【讨论】:

  • 问题是 __all__ 只有在你使用 from x import * 时才有帮助,这是你无论如何都不应该做的事情,而闭包技巧提供了非常简洁的内省。在 Plone 中,任何给定的名称都定义在数百个鸡蛋、数万个 python 文件和数百万行代码中,您实际上阅读我的代码并有机会注意到 @987654327 的可能性很小@ 或闭包。
【解决方案2】:

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

这也是你的闭包所做的。此外,它可能会混淆 IDE、大纲、pylint 等。

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

【讨论】:

  • from x import * 只能在 REPL 中使用。脆弱的代码不是错误?
【解决方案3】:

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

没有关闭:

# 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()并找到它吗?

【讨论】:

猜你喜欢
  • 2011-08-23
  • 2010-11-09
  • 1970-01-01
  • 2013-09-20
  • 2011-01-09
  • 1970-01-01
  • 1970-01-01
  • 2011-08-20
  • 1970-01-01
相关资源
最近更新 更多