`import module`的编码风格比`from module import function`更好吗?
我们把 from module import function
这种写法称为 FMIF 编码风格。
把 import module
称为 IM 编码风格。
把 from package import module
称为 FPIM 编码风格。
为什么 IM+FPIM 被认为比 FMIF 更好呢?(可以参考 这篇文章,它启发了这个问题。)
以下是我更喜欢 FMIF 而不是 IM 的一些理由:
- 代码简短:使用短一些的函数名,可以帮助我遵循每行 80 列的规范。
- 可读性:
chisquare(...)
比scipy.stats.stats.chisquare(...)
更容易阅读。虽然这是个主观标准,但我觉得大多数人会同意。 - 重定向的方便性:如果我使用 FMIF,之后想把 python 指向从
alt_module
而不是module
定义function
,我只需要改一行:from alt_module import function
。如果我用 IM,就得改很多行代码。
我对 IM+FPIM 比 FMIF 更好的所有理由都感兴趣,特别是我想了解以下几点 提到的内容:
IM 的优点:
- 在测试中更容易进行模拟/注入。(我对模拟不太熟悉,但最近了解了这个词的意思。能否给出代码示例,说明 IM 比 FMIF 更好?)
- 模块可以灵活地通过重新定义某些条目进行更改。(我可能理解错了,因为这似乎是 FMIF 比 IM 更好的一个优点。请参考我上面提到的 FMIF 的第三个理由。)
- 在序列化和恢复数据时,行为可预测且可控。(我真的不明白选择 IM 或 FMIF 如何影响这个问题。请详细说明。)
- 我知道 FMIF “污染了我的命名空间”,但除了这个听起来不好的说法,我不明白这具体对代码有什么伤害。
附言:在写这个问题时,我收到警告说这个问题看起来主观,可能会被关闭。请不要关闭它。我不是在寻找主观意见,而是想要具体的编码情况,说明 IM+FPIM 明显比 FMIF 更好。
非常感谢。
5 个回答
这里有很多很棒的回答(我都点赞了),以下是我对这个问题的一些看法:
首先,逐条回应你们的观点:
FMIF的(据说的)优点:
- 代码简短:短的函数名有助于保持每行80列的限制。
也许吧,但模块名通常已经够短了,所以这个问题不太重要。比如有 datetime
,但也有 os
、re
、sys
等等。而且在Python中,{ [ (
里面可以自由换行。对于嵌套模块,使用 as
在IM和FPIM中总是可以的。
- 可读性:chisquare(...) 看起来比 scipy.stats.stats.chisquare(...) 更易读。
我强烈不同意。当我阅读别人的代码(或者几个月后再看自己的代码)时,很难知道每个函数来自哪里。使用完整的名称可以让我避免在2345行和模块声明之间来回跳转。而且这也给了你一个上下文:“chisquare
? 这是什么?哦,来自 scipy
? 好吧,那应该是一些数学相关的东西。” 再说一次,你总是可以缩写 scipy.stats.stats as scypyst
。scypyst.chisquare(...)
这样也足够短,同时又保留了完整名称的好处。
import os.path as osp
是另一个好例子,因为通常我们会在一次调用中链式调用3个或更多的函数:join(expanduser(),basename(splitext())) 等等。
- 重定向的便利性:可以用一行代码重新定义来自altmodule的函数,而不是整个模块。
你有多少次想要重新定义一个函数而不是整个模块?模块的边界和函数的协调应该被保留,Alex已经对此进行了深入的解释。在大多数(所有?)实际场景中,如果 alt_module.x
可以替代 module.x
,那么很可能 alt_module
本身就是 module
的一个替代品,所以IM和FPIM也可以像FMIF一样用一行代码实现,只要你使用 as
。
- 我意识到FPIM在一定程度上解决了前两个问题……
实际上,as
才是缓解前两个问题(以及第三个问题)的关键,而不是FPIM。你也可以用IM来做到这一点:import some.long.package.path.x as x
,效果和FPIM一样。
所以以上这些其实并不是FMIF的优点。我更喜欢IM/FPIM的原因是:
为了简单和一致,当我导入某个东西时,无论是IM还是FPIM,我总是导入一个模块,而不是模块中的一个对象。记住,FMIF可以(滥)用来导入函数、类、变量,甚至其他模块!想想看 from somemodule import sys, somevar, os, SomeClass, datetime, someFunc
这会多乱。
而且,如果你想从一个模块中获取多个对象,FMIF会比IM或FPIM更容易污染你的命名空间,因为后者只会使用一个名称,无论你想用多少个对象。而且这些对象会有一个完整的名称,这其实是个优点,不是缺点:正如我在第二个问题中所说的,我认为这提高了可读性。
归根结底,这一切都关乎一致性、简单性和组织性。“导入模块,而不是对象” 是一个很好的、简单的思维模型,值得坚持。
关于这个话题,经典的观点来自Fredrik Lundh,也就是大家熟知的effbot。他的建议是:总是使用import——除非你有特别的理由不这样做。
换句话说,就是要有常识。个人来说,我发现如果模块层级比较深,通常会用 from x.y.z import a
的方式来导入,最常见的例子就是Django的模型。不过,这更多的是一种风格问题,你应该保持一致性——特别是像 datetime
这样的模块,因为它的模块名和里面的类名是一样的。你需要写 datetime.datetime.now()
还是只写 datetime.now()
呢?(在我的代码中,我总是写前者。)
你提到的第1和第2个问题似乎是同一个问题。Python的动态特性意味着无论你用哪种方法,替换模块中的某个项都相对简单。困难在于,如果一个模块中的函数引用了另一个函数,而你想要替换的正是那个被引用的函数。在这种情况下,导入整个模块而不是单独的函数,就可以用 module.function_to_replace = myreplacementfunc
的方式来替换,这样一切都能顺利进行——不过通过FPIM和IM这两种方式做到这一点都是一样简单的。
我也不太明白第3个问题和其他问题有什么关系。不过,我觉得你的第4个问题有点误解。你提到的这些方法都不会“污染你的命名空间”。真正会造成这种情况的是 from module import *
,因为你根本不知道自己导入了什么,这样代码中可能会出现一些函数,而读者完全不知道这些函数是从哪里来的。这种情况很糟糕,应该尽量避免。
你提到的IM/FPIM的一些缺点,通常可以通过合理使用as
关键字来改善。比如说,使用from some.package import mymodulewithalongname as mymod
可以有效地缩短代码,并提高可读性。如果你明天把mymodulewithalongname
改成somethingcompletelydifferent
,你只需要修改这条as
语句,就能一次性解决问题。
考虑一下你支持FMIF的第三点(我们称之为R,代表重定向)和你支持FPIM的第二点(我们称之为F,代表灵活性):R实际上是让模块边界的完整性受到影响,而F则是增强了这种完整性。一个模块里的多个函数、类和变量通常是为了协同工作而设计的,它们不应该被随意更改成不同的含义。例如,想想random
模块及其函数seed
和uniform
:如果你把其中一个函数的导入改成了其他模块,就会打破seed
和uniform
之间正常的联系。当一个模块设计得很好,具有内聚性和完整性时,R所带来的模块边界的破坏实际上是个坏事——它让你更容易做一些其实最好不要做的事情。
反过来,F则使得可以对相关的函数、类和变量进行协调的切换(也就是说,能够对那些属于同一模块的实体进行切换)。例如,为了让测试可重复(FPIM的优点之一),你可以在random
模块中同时模拟seed
和random
,如果你的代码遵循FPIM,那就没问题,协调性有保障;但如果你的代码直接导入了这些函数,你就得一个一个去找这些模块,并重复模拟,麻烦得很。要让测试完全可重复,通常还需要对日期和时间函数进行“协调模拟”——如果你在某些模块中使用了from datetime import datetime
,你需要找到并模拟所有这些模块(还有那些使用from time import time
的模块),以确保系统各部分在询问“现在几点了?”时,得到的时间是一致的(如果你使用FPIM,你只需模拟这两个相关模块)。
我喜欢FPIM,因为使用多重限定名称并没有带来太多额外的价值,相比之下,单一限定名称就足够了(而且,简单名称和限定名称之间的差别是巨大的——使用限定名称,无论是单一还是多重,你能获得的控制权远远超过简单名称!)。
唉,不能把整个工作日都用来回应你每一个观点——你的问题可能应该分成六个问题;-)。我希望这至少能解答“为什么F比R更好”以及一些模拟/测试的问题——归根结底就是保持和增强良好设计的模块性(通过F),而不是削弱它(通过R)。