“导入模块”的编码样式是否比“来自模块导入功能”的编码样式更好?


68

from module import function被称为FMIF的编码风格。

import module被称为IM的编码风格。

from package import module被称为FPIM的编码风格。

为什么IM + FPIM被认为比FMIF更好的编码风格?(有关问题的启发,请参阅此帖子。)

以下是一些使我更喜欢FMIF而非IM的条件:

  1. 代码的简短性:它使我可以使用较短的函数名称,从而有助于遵循每行80列的约定。
  2. 可读性:chisquare(...)比更具可读性 scipy.stats.stats.chisquare(...)。尽管这是一个主观标准,但我认为大多数人都会同意。
  3. 缓解重定向:如果我使用FMIF出于某种原因,在一段时间后要重定向蟒蛇定义functionalt_module不是module我需要改变只有一条线路:from alt_module import function。如果要使用IM,则需要更改许多代码行。
我意识到FPIM可以使前两个问题无效,但是第三个问题又如何呢?

我对IM + FPIM可能比FMIF更好的所有原因都感兴趣,但是特别是,我对这里提到的以下几点感兴趣:

IM的优点:

  1. 易于在测试中进行模拟/注入。(尽管我最近对模拟不是很熟悉,但是我最近才了解该术语的含义。在这里,您可以显示代码来证明IM比FMIF更好吗?)
  2. 通过重新定义一些条目,模块可以灵活更改的功能。(我一定会误会,因为这似乎是FMIF相对于IM的优势。请参见上面支持FMIF的第三个理由。)
  3. 有关数据序列化和恢复的可预测和可控制的行为。(我真的不明白IM或FMIF的选择如何影响此问题。请详细说明。)
  4. 我知道FMIF会“污染我的名称空间”,但是除了听起来像是一个否定性的短语外,我不理解这会如何以任何具体的方式伤害代码。
PS。在撰写此问题时,我收到警告,说该问题看起来很主观,很可能已被关闭。请不要关闭它。我并不是在寻找主观意见,而是要寻找IM + FPIM比FMIF更好的具体编码情况。

非常感谢。


2
顺便说一句,尽管它确实不会对您的编码实践产生太大影响,但是每个“。”。每次执行python中的dot调用实际上就是一项操作。因此,如果您要循环执行某项操作,则要确保本地代码在执行IM或FPIM时会执行rand = random.uniform之类的操作,而效率开始成为问题。
名叫

Answers:


46

您为IM / FPIM列出的否定词通常可以通过适当使用as子句来改善。 from some.package import mymodulewithalongname as mymod可以有效地缩短代码并提高其可读性,如果重命名mymodulewithalongnamesomethingcompletelydifferent明天,则该as子句可以用作单个语句进行编辑。

考虑您的FMIF前点3(重定向时称为R)与FPIM的前角2(灵活性时称为F):R等于促进模块边界完整性的丧失,而F则增强了模块边界的完整性。模块中的多个函数,类和变量通常旨在协同工作:它们不应独立切换为不同的含义。例如,考虑模块random及其功能,seed以及uniform:如果要将其中一个的导入切换到其他模块,则将中断对的调用seed和的调用结果之间的常规连接uniform。当一个模块设计得很好,具有内聚力和完整性时,R打破模块边界的便利实际上是不利的-它使您做得更好的事情变得更容易这样做。

反之亦然,F使耦合函数,类和变量(因此,通常,通过模块化属于同一实体的实体)之间的协调切换成为可能。例如,要使测试可重复(FPIM要点1),请在模块中和两者同时进行模拟,如果代码遵循FPIM,则一切就绪,可以保证协调。但是,如果您有直接导入函数的代码,则必须查找每个此类模块,并一遍又一遍地重复模拟。要使测试完全可重复,通常还需要对日期和时间函数进行“协调模拟”-如果在某些模块中使用,则需要查找并模拟所有这些(以及所有进行模拟的模拟)seedrandomrandomfrom datetime import datetimefrom time import time,依此类推),以确保当系统的各个部分询问“现在几点了?”时收到的所有时间。完全一致(如果使用FPIM,则只需模拟两个相关模块)。

我喜欢FPIM,因为通过使用有真的没有太多的附加价值乘以合格的名称,而不是一个单独合格的一个(而barenames和合格的名称之间的差异是巨大的-你会得到这样一个合格的名字更多的控制,无论是单或成倍增加,比起用一个裸名!

嗯,不能把所有的工作时间都花在回答您的每一个问题上-您的问题可能应该是六个问题;-)。我希望这至少能解决“为什么F比R更好”以及一些模拟/测试问题-归结为维护和增强设计良好的模块化(通过F)而不是破坏(通过R)。


random.seed影响random.uniform,但是FMIF允许更改seed而无需更改uniform。FMIF允许您在不小心的情况下以微妙的方式破坏代码!FPIM要求相关功能保持关联。这样可以防止上述错误的发生。啊,我终于明白了。谢谢。伙计,我有很多代码要重写... :)
unutbu

再次考虑,我可以编写一个python脚本为我完成工作!
unutbu

1
@unutbu,是的,特别是如果您最喜欢的编辑器功能强大且可用Python编写脚本(例如vim),则可以有效地使某些“大量编辑”自动化(不过请确保稍后再运行所有单元测试;-)。
Alex Martelli,2009年

15
我注意到您的答案使用FMIF :from question.FMIF import point_2 as F, point_3 as R
直觉

好答案。也许有点密集?请参阅“导入最佳实践”的答案,以更简单地表述这些观点。
Lutz Prechelt 2015年

18

通常,这方面的经典文字来自effbot的Fredrik Lundh。他的建议:始终使用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 *,您根本不知道要导入的内容,因此函数可以出现在代码中,而不会告诉读者它们的来源。这太可怕了,应该不惜一切代价避免。


在我几乎所有的脚本中,我都import datetime, now = datetime.datetime.now可以方便地执行print(now(), 'reading file X')...
gerrit

@gerrit:如果您从一个模块中使用多个功能,则通常不是一个好主意:每次您问“ somefunc()2345行中的内容来自何处时,它都会污染名称空间,并迫使您从代码到模块声明来回切换。 ”
MestreLion

@MestreLion我只针对一个函数(类方法)执行此操作,即datetime.datetime.now。否则,我from X import Y仅用于软件包,因此Y仍然是一个模块。
gerrit

@gerrit:我从来没有datetime.dametime.now()经常使用它来烦人或保证它是一个例外。但是,考虑到这一点,确实
令人感到困惑的

6

很棒的答案(我全都赞成),这是我对此事的看法:

首先,解决您的每个项目符号:

(据称)FMIF的优点:

  • 代码简短:较短的函数名称有助于保持每行80列。

也许可以,但是模块名称通常足够短,因此这无关紧要。当然,还有datetime,而且osresys等和Python具有内免费换行{ [ (。对于嵌套模块,asIM和FPIM中始终都有

  • 可读性:chisquare(...)比scipy.stats.stats.chisquare(...)更具可读性。

强烈反对。在读取外来代码(或几个月后我自己的代码)时,很难知道每个函数的来源。合格的名称使我免于从第2345行到模块声明标头的往返。而且它还为您提供了上下文chisquare那是什么?哦,是scypy从哪里来的?好的,那是一些与数学有关的东西”。而且,您可以随时再次缩写scipy.stats.stats as scypystscypyst.chisquare(...)足够简短,具有合格名称的所有好处。

import os.path as osp 另一个很好的例子,考虑到在一次调用中将3个或多个函数链接在一起非常普遍:join(expanduser(),basename(splitext()))等。

  • 易于重定向:从altmodule而不是module中单行重新定义功能。

您要多久重新定义一个功能而不是整个模块?模块边界和功能协调应该保留,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污染您的名称空间更多,而IM或FPIM则将使用单个名称,无论您要使用多少个对象。这样的对象将具有一个合格的 名称,这是一个专业名称,而不是一个缺点:正如我在第2期中所说,恕我直言,它可以提高可读性。

归结为一致性,简单性和组织性。“导入模块,而不是对象”是一个很好的,易于使用的思维模型。


5

像Alex Martelli一样,我喜欢as在导入函数时使用。

我要做的一件事是在从同一模块导入的所有函数上使用一些前缀:

from random import seed as r_seed
from random import random as r_random

r_seed比类型短,random.seed但在某种程度上保留了模块边界。随便看看您的代码的人都可以看到r_seed()并且r_random()有机会嘲笑它们之间的关系。

当然,您始终可以简单地执行以下操作:

import random as r

然后使用r.random()r.seed(),这可能是这种情况的理想折衷方案。仅在从模块导入一个或两个函数时使用前缀技巧。当我想使用同一模块中的许多功能时,我将导入该模块,也许as用来缩短名称。


1

我最同意MestreLion的观点(因此表示赞成)。

我的观点:我经常检查自己不熟悉的代码,而仅仅通过查看函数不知道该函数来自哪个模块。

代码只写一次就可以读取很多次,因此可读性和可维护性胜过打字的难易程度。

同样,通常不是为了编码者的利益而编写代码,而是为了另一个实体的利益而编写代码。

比您更了解python但不熟悉python的人应该可以阅读您的代码。

全路径导入还可以更好地帮助IDE将您指向正在查看的函数或对象的正确来源。

由于所有这些原因以及MestreLion指出的原因,我得出结论,导入和使用完整路径是最佳实践。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.