我没有在Java中做过企业级开发,但我经常看到反向域名包命名约定。例如,对于一个 Stack Overflow 的 Java 包,你应该将代码放在 package com.stackoverflow
下面。
我刚刚遇到了一个使用类似Java的约定的Python包,我不确定支持和反对它的原因是什么,或者它们是否与Java在Python中以相同的方式适用。为什么你会更喜欢其中之一?这些原因是否跨越语言适用?
Python不这样做的原因是会出现问题--几乎所有其他子包都是其子包的"com"包的所有者是谁?通过文件系统层次结构建立包层次结构的Python方法与此规范根本不兼容。Java之所以可以这样做,是因为包层次结构是由提供给“package”语句的字符串文字的结构定义的,因此不需要在任何地方显式地存在“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
文件中进行的操作,共享顶级包的子包不能假定在两者安装时可以正常工作,除非它们专门编写成彼此兼容(至少在这个文件中)。这将是一场分发的噩梦,并且几乎无法实现包的嵌套的全部目的。这不是特定于反向域名包层次结构的,尽管它们提供了最明显的坏例子和(依我之见)在哲学上是有问题的--这真的是共享顶级包的实际问题,而非哲学问题,这是我的主要关注点。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将不会运行),第二种情况可以完成,但您将失去模块.py的初始运行,但第三种情况完全无法实现。 代码必须执行才能使variable
可用。 这是另一个主要观点:import
不仅解析名称空间,而且执行代码。
现在,如果每个分发的Python软件包都需要搜索com
文件夹,然后是domain
,依此类推,那么您就可以“摆脱”此问题,但这会使打包变得更加困难,破坏了拖放功能,让打包成为一个大麻烦。
__init__.py
文件中运行任意代码(这会防止以下情况发生),我希望Python能够进行详尽的树形遍历,以搜索com.domain2
模块。 - Pawel Batko你为什么更喜欢其中一种?
Python的风格更简单。Java的风格允许来自不同组织的同名产品。
“这些原因是否适用于所有语言?”
是的。您可以轻松地在Python中使用顶级包命名为“com”,“org”,“mil”,“net”,“edu”和“gov”,并将您的包作为子包放入其中。
编辑。 这样做会有一些复杂性,因为每个人都必须合作,不要在这些顶级包中添加自己的杂物。
Python没有开始这样做,因为名称空间冲突 - 从实际上讲 - 显得相当罕见。
Java开始这样做,因为开发Java的人预见到很多人愚蠢地选择相同的包名称,并需要解决冲突和所有权问题。
Java人士没有预见到开源社区会选择奇怪的独特名称以避免名称冲突。有趣的是,编写xml解析器的每个人似乎都不称其为“parser”。他们似乎称其为“Saxon”或“Xalan”或完全奇怪的东西。
django
的包,同时导入这两个包可能会很困难。在 Java 中似乎是不可能的,因为它没有导入名称重命名功能。 - cdlearycom/__init__.py
的编辑答案,请查看我关于为什么导入别名实际上并没有帮助的解释。 - Jeff ShannonJoel在《Joel on Software》中比较了两种公司成长的方法:Ben & Jerry's方法是从小做起、有机生长,而Amazon则是在一开始就筹集大量资金,并宣称自己覆盖范围广泛。
当Sun公司推出Java时,进行了宣传和炒作。Java被认为将取代其他编程语言。未来的软件开发大部分都会使用基于Web的Java小应用程序。这个设想引起了轰动,甚至还有乐队和小马驹。在这种情况下,提前建立一个基于互联网、对公司友好、具有地球范围的命名规则是明智的选择。
好吧,事实并没有像Sun公司希望的那样发展,但他们制定计划时采用的是成功即可颠覆的方案,我个人非常不喜欢这种做法。
Python最初是由Guido van Rossum开发的项目,而且相当长时间内社区对于如果van Rossum突然离开导致项目无法继续的问题感到担心。据我所知,该项目最初并没有计划去征服世界,也没有打算将其作为Web小应用程序语言。
因此,在这门语言的形成阶段,没有理由想要一个庞大的层次结构的命名方案。在这个更为非正式的社区中,人们会选择一个稍微有些异想天开的项目名称,并检查是否已经有人在使用它。(以英国喜剧节目的名字命名计算机语言可能是一种起点有趣的方式。)没有人认为有必要迎合大型而缺乏想象力、笨拙的命名方案。
这是一种很好的防止名称冲突的方法,充分利用了现有的域名系统,因此不需要额外的官僚主义或注册。 这很简单而且聪明。
通过反转域名,它还赋予了层次结构,这非常方便。 因此,您可以在末尾拥有子包。
唯一的缺点是名称的长度,但对我来说这根本不是缺点。 我认为这对于支持它的任何语言都是一个不错的想法。
例如,为什么JavaScript库不这样做呢? 它们的全局命名空间是个大问题,但Javascript库使用简单的全局标识符(如'$'),这与其他Javascript库发生冲突。
这个想法是为了避免名称空间冲突。与不可读的UUID或类似的名称不同,反向域名不太可能妨碍其他人。 非常简单,但实用。 此外,在使用第三方库时,它可能会为您提供它们来自何处的线索(用于更新、支持等)。
Java可以像这样做,因为这是一种推荐的Java标准实践,并且被Java社区普遍接受。Python没有这个约定。
akka_http
和akka_actor
(你想要分别分发它们),而不是akka.http
和akka.actor
包,因为后者会遇到akka
包中多个__init__.py
的问题。如果你将包作为单个分发包进行分发,就不会遇到这个问题。对我来说,这似乎很不自然,也就是说,你必须根据计划如何分发它们来调整包的命名。 - Pawel Batko