为什么像Java这样的语言使用分层包名称,而Python不使用?

15

我没有在Java中做过企业级开发,但我经常看到反向域名包命名约定。例如,对于一个 Stack Overflow 的 Java 包,你应该将代码放在 package com.stackoverflow 下面。

我刚刚遇到了一个使用类似Java的约定的Python包,我不确定支持和反对它的原因是什么,或者它们是否与Java在Python中以相同的方式适用。为什么你会更喜欢其中之一?这些原因是否跨越语言适用?

8个回答

18

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文件中进行的操作,共享顶级包的子包不能假定在两者安装时可以正常工作,除非它们专门编写成彼此兼容(至少在这个文件中)。这将是一场分发的噩梦,并且几乎无法实现包的嵌套的全部目的。这不是特定于反向域名包层次结构的,尽管它们提供了最明显的坏例子和(依我之见)在哲学上是有问题的--这真的是共享顶级包的实际问题,而非哲学问题,这是我的主要关注点。
(另一方面,使用子包来更好地组织自己的单个大型包是一个很好的想法,因为这些子包是专门设计为相互配合和生活在一起的。虽然这在Python中并不常见,因为单个概念包往往不需要足够多的文件以需要额外的组织层次结构。)

1
您不必使用反向域名作为包名(在英国这很麻烦 - 我们使用“uk.co.company”吗,这很糟糕吗?)这只是一种设置非正式命名空间系统的方法,到目前为止它已经运行良好,我只是看不到您的论点。 - JeeBee
在Python中,你必须自己设计概念分发包层次的编码方式。也就是说,你最终会创建akka_httpakka_actor(你想要分别分发它们),而不是akka.httpakka.actor包,因为后者会遇到akka包中多个__init__.py的问题。如果你将包作为单个分发包进行分发,就不会遇到这个问题。对我来说,这似乎很不自然,也就是说,你必须根据计划如何分发它们来调整包的命名。 - Pawel Batko

14
如果Guido本人宣布应该遵循反向域约定,除非Python的import实现有重大变化,否则不会被采纳。
考虑一下: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将不会运行),第二种情况可以完成,但您将失去模块.py的初始运行,但第三种情况完全无法实现。 代码必须执行才能使variable可用。 这是另一个主要观点:import不仅解析名称空间,而且执行代码。

现在,如果每个分发的Python软件包都需要搜索com文件夹,然后是domain,依此类推,那么您就可以“摆脱”此问题,但这会使打包变得更加困难,破坏了拖放功能,让打包成为一个大麻烦。


从com.domain1导入模块作为d1m #等等。 - Ryan Ginstrom
不同路径目录下存在多个“com”包的问题确实很好 -- 我一直只考虑共享单个父包的问题,但是同名的独立包问题更加糟糕。 - Jeff Shannon
Java在运行时搜索类,不仅在编译时使用目录结构,它是动态链接的。 - Marian
如果不可能在__init__.py文件中运行任意代码(这会防止以下情况发生),我希望Python能够进行详尽的树形遍历,以搜索com.domain2模块。 - Pawel Batko

13

你为什么更喜欢其中一种?

Python的风格更简单。Java的风格允许来自不同组织的同名产品。

“这些原因是否适用于所有语言?”

是的。您可以轻松地在Python中使用顶级包命名为“com”,“org”,“mil”,“net”,“edu”和“gov”,并将您的包作为子包放入其中。

编辑。 这样做会有一些复杂性,因为每个人都必须合作,不要在这些顶级包中添加自己的杂物。

Python没有开始这样做,因为名称空间冲突 - 从实际上讲 - 显得相当罕见。

Java开始这样做,因为开发Java的人预见到很多人愚蠢地选择相同的包名称,并需要解决冲突和所有权问题。

Java人士没有预见到开源社区会选择奇怪的独特名称以避免名称冲突。有趣的是,编写xml解析器的每个人似乎都不称其为“parser”。他们似乎称其为“Saxon”或“Xalan”或完全奇怪的东西。


没错,我从来没有遇到过两个同名包之间的冲突。但是,你可能有两个名为 django 的包,同时导入这两个包可能会很困难。在 Java 中似乎是不可能的,因为它没有导入名称重命名功能。 - cdleary
鉴于Python的文件系统到包的映射,使用顶级包如“com”和“org”实际上会创建命名空间冲突。 - Jeff Shannon
@Jeff S:听起来像是一个很棒的开头! - cdleary
@Jeff S 但是这不是Python在导入模块时为什么会给你更严格的命名空间控制吗?import com as com2, from com import sub as sub1。在我看来,这比不得不回退到完全限定的类名要好得多。 - Joe Holloway
@jholloway7:关于com/__init__.py的编辑答案,请查看我关于为什么导入别名实际上并没有帮助的解释。 - Jeff Shannon

13

Joel在《Joel on Software》中比较了两种公司成长的方法:Ben & Jerry's方法是从小做起、有机生长,而Amazon则是在一开始就筹集大量资金,并宣称自己覆盖范围广泛。

当Sun公司推出Java时,进行了宣传和炒作。Java被认为将取代其他编程语言。未来的软件开发大部分都会使用基于Web的Java小应用程序。这个设想引起了轰动,甚至还有乐队和小马驹。在这种情况下,提前建立一个基于互联网、对公司友好、具有地球范围的命名规则是明智的选择。

好吧,事实并没有像Sun公司希望的那样发展,但他们制定计划时采用的是成功即可颠覆的方案,我个人非常不喜欢这种做法。

Python最初是由Guido van Rossum开发的项目,而且相当长时间内社区对于如果van Rossum突然离开导致项目无法继续的问题感到担心。据我所知,该项目最初并没有计划去征服世界,也没有打算将其作为Web小应用程序语言。

因此,在这门语言的形成阶段,没有理由想要一个庞大的层次结构的命名方案。在这个更为非正式的社区中,人们会选择一个稍微有些异想天开的项目名称,并检查是否已经有人在使用它。(以英国喜剧节目的名字命名计算机语言可能是一种起点有趣的方式。)没有人认为有必要迎合大型而缺乏想象力、笨拙的命名方案。


1
只翻译文本内容:+10 历史背景和清晰的视角对于编程非常重要,但是我只能给 +1 :) - lprsd
我会尽我所能支持加10分 :) - Jeff Shannon
个人而言,我鄙视那些可能因成功而被破坏的项目。我大多数情况下同意(尽管“鄙视”这个词有点过于强烈),但与此同时,那些承诺拯救和唯一正确之路的公司/项目也让我感到不安... - Jeff Shannon

11

这是一种很好的防止名称冲突的方法,充分利用了现有的域名系统,因此不需要额外的官僚主义或注册。 这很简单而且聪明。

通过反转域名,它还赋予了层次结构,这非常方便。 因此,您可以在末尾拥有子包。

唯一的缺点是名称的长度,但对我来说这根本不是缺点。 我认为这对于支持它的任何语言都是一个不错的想法。

例如,为什么JavaScript库不这样做呢? 它们的全局命名空间是个大问题,但Javascript库使用简单的全局标识符(如'$'),这与其他Javascript库发生冲突。


JavaScript是一个完全不同的问题。Python具有文件级模块作用域和导入机制,而JavaScript则没有。 - cdleary
啊,我不知道Python可以做到那个。不过关于Javascript的评论仍然相关。在Javascript中,对象存在于全局作用域中,Java中的包名也是如此,因此需要一些合理的命名约定来防止冲突。 - thomasrutter
好的,这很有道理——命名约定用于避免Java包导入中的冲突。但在Python中是否必要/有用呢? - cdleary
抱歉,我不了解足够的Python来发表评论 - 我尝试阅读了这篇文章http://www.network-theory.co.uk/docs/pytut/PythonScopesandNameSpaces.html ,它似乎表明在Python中,至少对于模块而言,这是不必要的。 - thomasrutter

6

这个想法是为了避免名称空间冲突。与不可读的UUID或类似的名称不同,反向域名不太可能妨碍其他人。 非常简单,但实用。 此外,在使用第三方库时,它可能会为您提供它们来自何处的线索(用于更新、支持等)。


啊,我忘了Java没有导入重命名机制(http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4983159),因此可能存在冲突的可能性。这可能是原因。 - cdleary
嗯,那会很不错!导入my.code.Enquiry; 导入their.code.Enquiry as TheirEnquiry;在类体中甚至不一定会有变量名/关键字冲突,因为它在导入部分。 - JeeBee

2
Python确实有这个功能,只是层次结构比较扁平。例如,看看os.path。而且没有什么阻止库设计者制作更深的层次结构,例如Django。
从根本上讲,我认为Python的设计理念是希望在不必预先指定或输入太多内容的情况下完成任务。这对于脚本编写和命令行使用非常有帮助。 Python之禅中有几部分涉及到了这一点:
- 简单比复杂好。 - 扁平比嵌套好。 - 美丽比丑陋好。(Java系统对我来说很丑陋)
另一方面,还有:
- 命名空间是一个伟大的想法 - 让我们做更多的那些!

我认为你回答的是问题的标题而不是内容——我真正想知道的是反向域名层次结构。 - cdleary

0

Java可以像这样做,因为这是一种推荐的Java标准实践,并且被Java社区普遍接受。Python没有这个约定。


我以为Python是完美的? :) - willcodejavaforfood
1
Python是完美的。只是Java使用了一种错误的惯例 ;) - Oli
是的 - 我的问题是在问为什么。 - cdleary
2
@mP:这个问题是关于约定的。支持这两种方法的确切理由都存在。对于Java来说,一个好处就是它可以很清晰地导航包,并清楚地看到每个包所属的位置。 - Avi
我不确定为什么我的投票一直被否决 - 有人能给出任何理由吗? - Avi
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接