我没有在 Java 中做过企业工作,但我经常看到反向域名包命名约定。例如,对于 Stack Overflow Java 包,您可以将代码放在 package 之下com.stackoverflow
。
我刚刚遇到了一个使用类似 Java 约定的 Python 包,我不确定支持和反对它的论点是什么,或者它们是否以与 Java 相同的方式适用于 Python。您更喜欢其中一个的原因是什么?这些原因是否适用于所有语言?
Python 不这样做是因为你最终会遇到一个问题——谁拥有几乎所有其他东西都是子包的“com”包?Python 建立包层次结构(通过文件系统层次结构)的方法根本不能很好地适应这种约定。Java 可以摆脱它,因为包层次结构是由提供给“包”语句的字符串文字的结构定义的,因此任何地方都不需要显式的“com”包。
还有一个问题是,如果您想公开发布一个软件包,但没有一个适合嵌入软件包名称的域名,或者您最终出于某种原因更改(或丢失)您的域名,该怎么办。(以后的更新需要不同的包名吗?你怎么知道 com.nifty_consultants.nifty_utility 是 com.joe_blow_software.nifty_utility 的更新版本?或者相反,你怎么知道它不是更新版本?如果你错过了你的域名更新,域名被域名营员抢走,别人从他们那里购买了域名,他们想公开发布软件包,他们是否应该使用你已经使用过的同一个名字?)
在我看来,域名和软件包名称解决了两个完全不同的问题,并且具有完全不同的复杂因素。我个人不喜欢 Java 的约定,因为(恕我直言)它违反了关注点分离。避免命名空间冲突是一件好事,但我讨厌我的软件的命名空间是由(并依赖于)营销部门与某些第三方官僚机构的交互定义的。
为了进一步澄清我的观点,回应 JeeBee 的评论:在 Python 中,包是一个包含__init__.py
文件(并且可能是一个或多个模块文件)的目录。包层次结构要求每个更高级别的包都是完整的、合法的包。如果两个包(特别是来自不同供应商,但甚至来自同一供应商的不直接相关的包)共享一个顶级包名称,无论该名称是 'com' 或 'web' 还是 'utils' 或其他任何名称,每一个必须__init__.py
为该顶级包提供一个。我们还必须假设这些包很可能安装在目录树中的相同位置,即site-packages/[pkg]/[subpkg]。文件系统因此强制只有一个 [pkg]/__init__.py
——那么谁赢了?这个问题没有(也不可能)一个一般情况下的正确答案。我们也不能合理地将这两个文件合并在一起。由于我们不知道另一个包可能需要在其中做什么,__init__.py
因此不能假定共享顶级包的子包在安装两者时都可以工作,除非它们被专门编写为相互兼容(至少在这个文件中) )。这将是一场分发噩梦,并且几乎会使嵌套包的整个点无效。这并不特定于反向域名包层次结构,尽管它们提供了最明显的坏例子并且(IMO)在哲学上是有问题的——这实际上是共享顶级包的实际问题,而不是哲学问题,即我主要关心这里。
(另一方面,使用子包更好地组织自身的单个大包是一个好主意,因为这些子包是专门设计用于一起工作和生活的。但这在 Python 中并不常见,因为单个概念包没有t 往往需要足够多的文件才能需要额外的组织层。)
如果 Guido 自己宣布应该遵循反向域约定,它不会被采用,除非import
在 python 中的实现有重大变化。
考虑:python 在运行时使用快速失败算法搜索导入路径;java 在编译时和运行时都使用详尽的算法搜索路径。来吧,尝试像这样安排你的目录:
folder_on_path/
com/
__init__.py
domain1/
module.py
__init__.py
other_folder_on_path/
com/
__init__.py
domain2/
module.py
__init__.py
然后尝试:
from com.domain1 import module
from com.domain2 import module
这些语句中的一个将成功。为什么?因为在搜索路径上要么folder_on_path
或要么更高。other_folder_on_path
当 python 看到from com.
它可以抓取第一个com
包时。如果恰好包含domain1
,那么第一个import
将成功;如果没有,它会抛出一个ImportError
并放弃。为什么?因为import
必须在运行时发生,可能发生在代码流中的任何一点(尽管最常见于开始时)。没有人想要在这一点上进行详尽的树遍历来验证没有可能的匹配。它假设如果它找到一个名为 的包com
,它就是这个 com
包。
此外,python 不区分以下语句:
from com import domain1
from com.domain1 import module
from com.domain1.module import variable
验证的概念com
在每种 com
情况下都会有所不同。在java中,你真的只需要处理第二种情况,这可以通过遍历文件系统来完成(我猜类和文件命名相同的好处)。在 python 中,如果您尝试仅在文件系统帮助的情况下完成导入,第一种情况可能(几乎)透明地相同(init .py 不会运行),第二种情况可以完成,但您会丢失初始运行module.py,但第三种情况是完全无法实现的。代码必须执行variable
才能可用。这是另一个要点:import
不仅仅是解析命名空间,它还执行代码。
现在,如果每个分发的 python 包都需要一个安装过程来搜索文件夹,然后是, 等等,那么你可以摆脱这个,但这使得打包变得相当困难,破坏了拖放功能,并使包装和全面滋扰。com
domain
“你更喜欢其中一个的原因是什么?”
Python 的风格更简单。Java 的风格允许来自不同组织的同名产品。
“这些理由是否适用于所有语言?”
是的。您可以轻松地拥有名为“com”、“org”、“mil”、“net”、“edu”和“gov”的顶级 Python 包,并将您的包作为子包放入其中。
编辑. 当你这样做的时候你有一些复杂性,因为每个人都必须合作而不是用他们自己的垃圾污染这些顶级包。
Python 没有开始这样做是因为命名空间冲突——作为一个实际问题——被证明是相当罕见的。
Java 开始这样做是因为开发 Java 的人预见到很多人会毫无头绪地为他们的包选择相同的名称,并且需要解决冲突和所有权问题。
Java 人员没有预见到开源社区会选择奇怪的现成唯一名称来避免名称冲突。有趣的是,每个编写 xml 解析器的人都不称其为“解析器”。他们似乎称它为“撒克逊”或“Xalan”或完全奇怪的东西。
在 Joel on Software 的某处,Joel 比较了两种公司发展方式:Ben & Jerry 的方法,从小规模开始有机地发展,以及亚马逊的方法,从一开始就筹集大量资金并提出非常广泛的要求.
当 Sun 推出 Java 时,它大张旗鼓,大肆宣传。Java应该接管。大多数未来相关的软件开发将在网络交付的 Java 小程序上进行。会有铜管乐队,甚至是小马。在这种情况下,明智的做法是预先建立一个基于互联网、对公司友好且在全球范围内的命名约定。
好吧,结果并不像 Sun 所希望的那样,但他们计划着,好像他们会成功一样。就我个人而言,我鄙视那些可能被成功破坏的项目。
Python 最初是 Guido van Rossum 的一个项目,社区在很长一段时间后才确信如果 van Rossum 被公共汽车撞到它会生存下来。据我所知,最初并没有接管世界的计划,而且它也不是一种网络小程序语言。
因此,在语言的形成阶段,没有理由为命名方案需要一个庞大的层次结构。在那个更非正式的社区中,一个人选择了一个或多或少异想天开的项目名称,并检查其他人是否已经在使用它。(在英国喜剧节目之后命名一种计算机语言可能会被认为是异想天开的开始。)人们认为没有必要迎合一个庞大但缺乏想象力和笨拙的命名方案。
这是防止名称冲突的好方法,并充分利用现有的域名系统,因此不需要额外的官僚机构或注册。它简单而精彩。
通过反转域名,它还赋予了它一个层次结构,这很方便。所以你可以在最后有子包。
唯一的缺点是名称的长度,但对我来说这根本不是缺点。我认为对于任何支持它的语言来说,这都是一个不错的主意。
例如,为什么 JavaScript 库不这样做?它们的全局命名空间是一个大问题,但 Javascript 库使用简单的全局标识符,如“$”,这与其他 Javascript 库发生冲突。
这个想法是保持名称空间无冲突。而不是不可读的 UUID 或类似的,反向域名不太可能妨碍别人。非常简单,但务实。此外,当使用 3rd 方库时,它可能会为您提供关于它们来自何处的线索(用于更新、支持等)
Python确实有它,它只是一个更扁平的层次结构。看os.path
,例如。并且没有什么能阻止库设计者做出更深层次的设计,例如 Django。
从根本上说,我认为 Python 的设计理念是您希望完成工作而无需提前指定或输入太多内容。这极大地有助于脚本和命令行的使用。“The Zen of Python”中有几个部分解决了这个问题的基本原理:
另一方面,有:
Java 能够这样做,因为它是推荐的 Java 标准实践,并且几乎被 Java 社区普遍接受。Python 没有这个约定。