使用闭包而不是__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
VS。使用__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。
答案 0 :(得分:7)
我喜欢编写完全像死脑一样简单的代码。
__all__
是Python的一项功能,明确添加以解决限制模块可见的名称的问题。当你使用它时,人们会立即明白你在做什么。
你的封口技巧是非标准的,如果我遇到它,我不会立即理解它。你需要做一个长篇评论来解释它,然后你需要另外做一个长评论来解释你为什么这样做而不是使用__all__
。
在Python中,在模块中使用下划线为私有名称加前缀是个好习惯。如果您执行from the_module_name import *
,您将获得所有不以下划线开头的名称。因此,我宁愿看到正确使用初始下划线的习语,而不是封闭技巧。
请注意,如果您使用初始下划线名称,则甚至不需要使用__all__
。
答案 1 :(得分:4)
from x import *
的问题在于它可以隐藏NameErrors
,这使得难以追踪琐碎的错误。 “命名空间污染”意味着在命名空间中添加内容,而您不知道它来自何处。
你的关闭也是如此。此外,它可能会混淆IDE,轮廓,pylint等。
使用模块的“错误”名称也不是一个真正的问题。模块对象与导入它们的位置相同。如果“错误”的名称消失(更新后),应该清楚为什么并激励程序员下次正确地执行此操作。但它不会导致错误。
答案 2 :(得分:1)
好的,我开始更多地了解这个问题了。封闭确实允许隐藏私人物品。这是一个简单的例子。
没有封闭:
# 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()
创建的函数对象的引用,或者它是否仍然存在对仍然存在的名称空间的引用,这样它可以查找名称{{1}}并找到它?