【问题标题】:Use 'import module' or 'from module import'?使用“导入模块”还是“从模块导入”?
【发布时间】:2010-10-17 04:03:38
【问题描述】:

我试图找到一份关于最好使用import module 还是from module import 的综合指南。我刚开始使用 Python,我正在尝试从最佳实践入手。

基本上,我希望有人能分享他们的经验、其他开发人员的偏好以及避免任何陷阱的最佳方法是什么?

【问题讨论】:

  • 我只是想让你知道,选择的答案是错误的。它指出差异是主观的,而存在差异。这可能会导致难以检测到错误。请参阅 Michael Ray Lovetts 的回答。
  • 导入特定命名标识符 'from module import X,Y,Z'from module import * 之间存在天壤之别。后者会污染您的命名空间,并且可能会根据模块中发生的情况给出不可预测的结果。更糟糕的是 from module import * 使用多个模块。
  • Python 语言没有标准,但有一本不错的书,来自 Python 3.*,主要是由 Gvinno Van Rossum(该语言的作者)最初编写的 Python 参考:docs.python.org/3/tutorial
  • 而且我不知道为什么人们还要写另外 1'000'000'000 个关于该语言的教程,而有好书可以在 4-5 天内学习该语言。

标签: python python-import


【解决方案1】:

两种方式都受支持是有原因的:有时一种方式比另一种方式更合适。

  • import module:当您使用模块中的许多位时很好。缺点是您需要使用模块名称来限定每个引用。

  • from module import ...:很高兴导入的项目无需模块名称前缀即可直接使用。缺点是你必须列出你使用的每一个东西,而且在代码中并不清楚这些东西是从哪里来的。

使用哪个取决于哪个使代码清晰易读,并且与个人喜好有很大关系。我倾向于import module 通常是因为在代码中非常清楚对象或函数的来源。当我在代码中使用一些对象/函数时,我使用from module import ...

【讨论】:

  • 有没有办法使用from M import X 并且仍然以某种方式获得使用限定符的好处?如果您仍然可以在导入之后执行M.X,那么您似乎可以两全其美。
  • @artgropod:有点。你可以做class m: from something.too.long import x, y, z。不过真的不建议这样做。
【解决方案2】:

import modulefrom module import foo 之间的区别主要是主观的。选择您最喜欢的一个,并在使用时保持一致。以下几点可以帮助您做出决定。

import module

  • 优点:
    • 减少对import 语句的维护。无需添加任何额外的导入即可开始使用模块中的其他项目
  • 缺点:
    • 在代码中输入 module.foo 可能会很乏味且多余(使用 import module as mo 然后输入 mo.foo 可以最大限度地减少乏味)

from module import foo

  • 优点:
    • 减少输入以使用foo
    • 对可以访问模块的哪些项目进行更多控制
  • 缺点:
    • 要使用模块中的新项目,您必须更新您的 import 声明
    • 您失去了关于 foo 的上下文。例如,与math.ceil() 相比,ceil() 的作用不太清楚

任何一种方法都可以,但不要使用from module import *

对于任何合理的大型代码集,如果您 import * 您可能会将其固定到模块中,无法删除。这是因为很难确定代码中使用的哪些项目来自“模块”,因此很容易达到您认为不再使用import 但很难确定的地步.

【讨论】:

  • +1 用于阻止使用“from module import *”,它只会使命名空间变得混乱。
  • 使命名空间混乱不是“import *”中最成问题的部分,它降低了可读性:任何名称冲突都会在(单元)测试中显示出来。但是您从导入的模块中使用的所有名称都将是空的,没有任何提示它们来自。我绝对讨厌“import *”。
  • Python之禅不是说显式优于隐式吗?
  • from module import * 可能特别有用,如果将其用作:if(windows):\n\t from module_win import * \n else: \n\t from module_lin import *。如果 module_lin 和 module_win 中的函数名称相同,那么您的父模块可能包含与操作系统无关的函数名称。这就像有条件地继承任一类。
  • @anishsane。还有另一种方法。导入 module_win 作为东西。然后总是使用 something.method_name()
【解决方案3】:
import module

当您将使用模块中的许多功能时最好。

from module import function

当您只需要function 时,最好避免使用模块中的所有函数和类型污染全局命名空间。

【讨论】:

  • 如果你做'import module',全局命名空间中唯一的东西就是'module'吗?如果你做'from .. import *',你只会污染命名空间。
【解决方案4】:

我个人一直使用

from package.subpackage.subsubpackage import module

然后访问所有内容

module.function
module.modulevar

等等。原因是同时您的调用时间很短,并且您清楚地定义了每个例程的模块名称空间,如果您必须在源代码中搜索给定模块的使用情况,这将非常有用。

不用说,不要使用 import *,因为它会污染你的命名空间并且它不会告诉你给定的函数来自哪里(来自哪个模块)

当然,如果两个不同包中的两个不同模块的模块名称相同,你可能会遇到麻烦,比如

from package1.subpackage import module
from package2.subpackage import module

在这种情况下,你当然会遇到麻烦,但是强烈暗示你的包布局有缺陷,你必须重新考虑。

【讨论】:

  • 在最后一种情况下,您始终可以使用: import pkgN.sub.module as modN 为每个模块提供不同的名称。您还可以使用“import modulename as mod1”模式来缩短长名称,或通过单个名称更改在相同 API(例如 DB API 模块)的实现之间切换。
【解决方案5】:

补充一下人们对from x import * 的看法:除了让人们更难分辨名字的来源之外,这还会让 Pylint 等代码检查器失效。他们会将这些名称报告为未定义的变量。

【讨论】:

    【解决方案6】:

    我自己对此的回答主要取决于首先,我将使用多少不同的模块。如果我只打算使用一两个,我会经常使用 from ... import,因为这样可以减少其余部分的击键次数文件,但如果我要使用许多不同的模块,我更喜欢 import 因为这意味着每个模块引用都是自记录的。我可以看到每个符号的来源,而无需四处寻找。

    通常我更喜欢普通导入的自我记录风格,并且只有在我必须键入模块名称的次数超过 10 到 20 次时才更改为 from.. import,即使只有一个模块被导入。

    【讨论】:

      【解决方案7】:

      我刚刚发现了这两种方法之间的另一个细微差别。

      如果模块 foo 使用以下导入:

      from itertools import count
      

      那么模块bar 可以错误地使用count,就好像它是在foo 中定义的,而不是在itertools 中定义的:

      import foo
      foo.count()
      

      如果foo 使用:

      import itertools
      

      这个错误仍然是可能的,但不太可能发生。 bar 需要:

      import foo
      foo.itertools.count()
      

      这给我带来了一些麻烦。我有一个模块错误地从一个没有定义它的模块导入了一个异常,只从其他模块导入了它(使用from module import SomeException)。当不再需要并删除导入时,有问题的模块已损坏。

      【讨论】:

        【解决方案8】:

        这里还有一个未提及的细节,与写入模块有关。当然这可能不是很常见,但我不时需要它。

        由于引用和名称绑定在 Python 中的工作方式,如果您想从该模块外部更新模块中的某些符号,例如 foo.bar,并让其他导入代码“看到”该更改,您必须以某种方式导入 foo。例如:

        模块 foo:

        bar = "apples"
        

        模块一:

        import foo
        foo.bar = "oranges"   # update bar inside foo module object
        

        模块 b:

        import foo           
        print foo.bar        # if executed after a's "foo.bar" assignment, will print "oranges"
        

        但是,如果您导入符号名称而不是模块名称,这将不起作用。

        例如,如果我在模块 a 中这样做:

        from foo import bar
        bar = "oranges"
        

        a 之外的任何代码都不会将bar 视为“橙子”,因为我对bar 的设置仅影响了模块a 内的名称“bar”,它没有“触及”foo模块对象并更新其bar

        【讨论】:

        • 不,在最后一个例子中,名字'foo'是未知的
        • 这个答案提供了关于这个问题的“真实”答案:两个导入变体之间有什么区别
        • 写了一些sn-p来证明这个答案是绝对正确的,但这背后的理由是什么?
        • 这并不完全正确。所示的情况是字符串不可变的结果。如果"bar" 是一个列表,那么在模块 a 中说 "foo.bar.append('oranges')" 会在模块 b 中打印列表时反映出来。
        • @gateway2745 完全正确。这仅仅是阴影的一个例子。需要global 的函数也有同样的问题。
        【解决方案9】:

        这里还有一个没有提到的区别。这是从http://docs.python.org/2/tutorial/modules.html逐字复制的

        注意使用时

        from package import item
        

        项目可以是包的子模块(或子包),也可以是包中定义的其他名称,如函数、类或变量。 import 语句首先测试项目是否在包中定义;如果不是,它假定它是一个模块并尝试加载它。如果找不到,则会引发 ImportError 异常。

        相反,当使用像

        这样的语法时
        import item.subitem.subsubitem
        

        除最后一项外,每一项都必须是一个包裹;最后一项可以是模块或包,但不能是上一项中定义的类或函数或变量。

        【讨论】:

        • 我注意到的另一件事是,如果 item 也是包内的子模块,则“从包导入项目”有效,但“导入包”package.item.subitem=... 不适用于空包的init.py,除非我们在包的init文件中有“import item”。
        【解决方案10】:

        尽管很多人已经解释了 importimport from,但我想尝试更多地解释一下幕后发生的事情,以及它改变的所有地方。


        import foo:

        导入foo,并在当前命名空间中创建对该模块的引用。然后,您需要定义完整的模块路径以从模块内部访问特定的属性或方法。

        例如foo.bar 但不是bar

        from foo import bar:

        导入 foo,并创建对所有列出的成员 (bar) 的引用。不设置变量foo

        例如bar 但不是 bazfoo.baz

        from foo import *:

        导入foo,并创建对该模块在当前命名空间中定义的所有公共对象的引用(如果__all__ 存在,则在__all__ 中列出的所有对象,否则所有不以_ 开头的对象)。不设置变量foo

        例如barbaz 但不是 _quxfoo._qux


        现在让我们看看import X.Y

        >>> import sys
        >>> import os.path
        

        检查sys.modules 与名称osos.path

        >>> sys.modules['os']
        <module 'os' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/os.pyc'>
        >>> sys.modules['os.path']
        <module 'posixpath' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/posixpath.pyc'>
        

        使用osos.path 检查globals()locals() 命名空间字典:

        >>> globals()['os']
        <module 'os' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/os.pyc'>
        >>> locals()['os']
        <module 'os' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/os.pyc'>
        >>> globals()['os.path']
        Traceback (most recent call last):
          File "<stdin>", line 1, in <module>
        KeyError: 'os.path'
        >>>
        

        从上面的例子我们发现只有os被插入到本地和全局命名空间中。 所以,我们应该可以使用:

        >>> os
        <module 'os' from
          '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/os.pyc'>
        >>> os.path
        <module 'posixpath' from
         '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/posixpath.pyc'>
        >>>
        

        但不是path

        >>> path
        Traceback (most recent call last):
          File "<stdin>", line 1, in <module>
        NameError: name 'path' is not defined
        >>>
        

        从 locals() 命名空间中删除 os 后,您将无法访问 osos.path,即使它们存在于 sys.modules 中:

        >>> del locals()['os']
        >>> os
        Traceback (most recent call last):
          File "<stdin>", line 1, in <module>
        NameError: name 'os' is not defined
        >>> os.path
        Traceback (most recent call last):
          File "<stdin>", line 1, in <module>
        NameError: name 'os' is not defined
        >>>
        

        现在我们来谈谈import from

        from:

        >>> import sys
        >>> from os import path
        

        检查sys.modulesosos.path

        >>> sys.modules['os']
        <module 'os' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/os.pyc'>
        >>> sys.modules['os.path']
        <module 'posixpath' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/posixpath.pyc'>
        

        我们发现在sys.modules 中我们发现和之前使用import name 时一样

        好的,让我们看看它在 locals()globals() 命名空间字典中的样子:

        >>> globals()['path']
        <module 'posixpath' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/posixpath.pyc'>
        >>> locals()['path']
        <module 'posixpath' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/posixpath.pyc'>
        >>> globals()['os']
        Traceback (most recent call last):
          File "<stdin>", line 1, in <module>
        KeyError: 'os'
        >>>
        

        您可以使用名称path 而非os.path 访问:

        >>> path
        <module 'posixpath' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/posixpath.pyc'>
        >>> os.path
        Traceback (most recent call last):
          File "<stdin>", line 1, in <module>
        NameError: name 'os' is not defined
        >>>
        

        让我们从locals()中删除“路径”:

        >>> del locals()['path']
        >>> path
        Traceback (most recent call last):
          File "<stdin>", line 1, in <module>
        NameError: name 'path' is not defined
        >>>
        

        最后一个使用别名的例子:

        >>> from os import path as HELL_BOY
        >>> locals()['HELL_BOY']
        <module 'posixpath' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/posixpath.pyc'>
        >>> globals()['HELL_BOY']
        <module 'posixpath' from /System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/posixpath.pyc'>
        >>>
        

        并且没有定义路径:

        >>> globals()['path']
        Traceback (most recent call last):
         File "<stdin>", line 1, in <module>
        KeyError: 'path'
        >>>
        

        【讨论】:

        • 虽然这很冗长,但对于一个相当复杂的问题,这确实是列表中的最佳答案。它提供了实际代码来帮助解释这个特定问题的“幕后”微妙之处,这些细节比风格更重要。我希望我能多次投票!
        • 使用as SYMBOL 会改变这个答案的工作方式吗?
        【解决方案11】:
        import package
        import module
        

        对于import,令牌必须是模块(包含 Python 命令的文件)或包(sys.path 中包含文件 __init__.py 的文件夹。)

        当有分包时:

        import package1.package2.package
        import package1.package2.module
        

        文件夹(包)或文件(模块)要求相同,但文件夹或文件必须在package2内,package1内必须有package1package2都必须包含@ 987654331@ 文件。 https://docs.python.org/2/tutorial/modules.html

        使用from 风格的导入:

        from package1.package2 import package
        from package1.package2 import module
        

        包或模块以module(或package)而不是package1.package2.module 进入包含import 语句的文件的命名空间。您总是可以绑定到更方便的名称:

        a = big_package_name.subpackage.even_longer_subpackage_name.function
        

        只有from 风格的导入允许你命名一个特定的函数或变量:

        from package3.module import some_function
        

        是允许的,但是

        import package3.module.some_function 
        

        不允许。

        【讨论】:

          【解决方案12】:

          导入模块 - 您无需额外的努力即可从模块中获取其他内容。它有诸如重复输入等缺点

          模块导入自 - 更少的输入和更多的控制可以访问模块的哪些项目。要使用模块中的新项目,您必须更新导入语句。

          【讨论】:

            【解决方案13】:

            由于我也是初学者,我将尝试以简单的方式解释这一点: 在 Python 中,我们有三种类型的 import 语句,它们是:

            1.通用导入:

            import math
            

            这种类型的导入是我个人最喜欢的,这种导入技术的唯一缺点是如果你需要使用任何模块的功能,你必须使用以下语法:

            math.sqrt(4)
            

            当然,它会增加输入工作量,但作为初学者,它会帮助您跟踪与之相关的模块和功能,(一个好的文本编辑器会显着减少输入工作量,推荐)。

            使用这个 import 语句可以进一步减少打字工作:

            import math as m
            

            现在,您可以使用m.sqrt(),而不是使用math.sqrt()

            2。函数导入:

            from math import sqrt
            

            如果您的代码只需要访问模块中的单个或几个函数,则这种类型的导入最适合,但对于使用模块中的任何新项目,您必须更新导入语句。 p>

            3.通用导入:

            from math import * 
            

            虽然它显着减少了输入工作,但不推荐使用,因为它会使用模块中的各种函数填充您的代码,并且它们的名称可能与用户定义的函数的名称冲突。 示例:

            如果你有一个名为 sqrt 的函数并导入 math,你的函数是安全的:有你的 sqrt 和 math.sqrt。但是,如果您执行 from math import *,则会出现问题:即,两个不同的函数具有完全相同的名称。来源:Codecademy

            【讨论】:

              【解决方案14】:

              有些内置模块主要包含裸函数(base64mathosshutilsystime,...),这绝对是一个好习惯将这些裸函数绑定到某个命名空间,从而提高代码的可读性。考虑一下没有命名空间的情况下理解这些函数的含义是多么困难:

              copysign(foo, bar)
              monotonic()
              copystat(foo, bar)
              

              比绑定到某个模块时:

              math.copysign(foo, bar)
              time.monotonic()
              shutil.copystat(foo, bar)
              

              有时您甚至需要命名空间来避免不同模块之间的冲突(json.loadpickle.load


              另一方面,有一些模块主要包含类(configparserdatetimetempfilezipfile、...),其中许多模块的类名足以不言自明:
              configparser.RawConfigParser()
              datetime.DateTime()
              email.message.EmailMessage()
              tempfile.NamedTemporaryFile()
              zipfile.ZipFile()
              

              因此,在代码中使用这些带有附加模块命名空间的类是否会添加一些新信息或只是延长代码可能存在争议。

              【讨论】:

                【解决方案15】:

                我发现的一个显着差异令人惊讶地没有人谈论过,使用普通 import 您可以从导入的模块,使用 from-import 语句是不可能的。

                图片中的代码:

                setting.py

                public_variable = 42
                _private_variable = 141
                def public_function():
                    print("I'm a public function! yay!")
                def _private_function():
                    print("Ain't nobody accessing me from another module...usually")
                

                plain_importer.py

                import settings
                print (settings._private_variable)
                print (settings.public_variable)
                settings.public_function()
                settings._private_function()
                
                # Prints:
                # 141
                # 42
                # I'm a public function! yay!
                # Ain't nobody accessing me from another module...usually
                

                from_importer.py

                from settings import *
                #print (_private_variable) #doesn't work
                print (public_variable)
                public_function()
                #_private_function()   #doesn't work
                

                【讨论】:

                  【解决方案16】:

                  我想补充一下。如果遇到循环导入,了解 Python 如何将导入的模块作为属性处理会很有用。

                  我有以下结构:

                  mod/
                      __init__.py
                      main.py
                      a.py
                      b.py
                      c.py
                      d.py
                  

                  我将使用不同的导入方法从 main.py 导入其他模块

                  main.py:

                  import mod.a
                  import mod.b as b
                  from mod import c
                  import d
                  

                  dis.dis 显示差异(注意模块名称,a b c d):

                    1           0 LOAD_CONST               0 (-1)
                                3 LOAD_CONST               1 (None)
                                6 IMPORT_NAME              0 (mod.a)
                                9 STORE_NAME               1 (mod)
                  
                    2          12 LOAD_CONST               0 (-1)
                               15 LOAD_CONST               1 (None)
                               18 IMPORT_NAME              2 (b)
                               21 STORE_NAME               2 (b)
                  
                    3          24 LOAD_CONST               0 (-1)
                               27 LOAD_CONST               2 (('c',))
                               30 IMPORT_NAME              1 (mod)
                               33 IMPORT_FROM              3 (c)
                               36 STORE_NAME               3 (c)
                               39 POP_TOP
                  
                    4          40 LOAD_CONST               0 (-1)
                               43 LOAD_CONST               1 (None)
                               46 IMPORT_NAME              4 (mod.d)
                               49 LOAD_ATTR                5 (d)
                               52 STORE_NAME               5 (d)
                               55 LOAD_CONST               1 (None)
                  

                  最后它们看起来是一样的(STORE_NAME 是每个示例中的结果),但是如果您需要考虑以下四个循环导入,请注意这一点:

                  例子1

                  foo/
                     __init__.py
                     a.py
                     b.py
                  
                  a.py:
                  import foo.b 
                  
                  b.py:
                  import foo.a
                  
                  >>> import foo.a
                  >>>
                  

                  这行得通

                  例子2

                  bar/
                     __init__.py
                     a.py
                     b.py
                  
                  a.py:
                  import bar.b as b
                  
                  b.py:
                  import bar.a as a
                  
                  >>> import bar.a
                  Traceback (most recent call last):
                    File "<stdin>", line 1, in <module>
                    File "bar\a.py", line 1, in <module>
                      import bar.b as b
                    File "bar\b.py", line 1, in <module>
                      import bar.a as a
                  AttributeError: 'module' object has no attribute 'a'
                  

                  没有骰子

                  例子3

                  baz/
                     __init__.py
                     a.py
                     b.py
                  
                  a.py:
                  from baz import b
                  
                  b.py:
                  from baz import a
                  
                  >>> import baz.a
                  Traceback (most recent call last):
                    File "<stdin>", line 1, in <module>
                    File "baz\a.py", line 1, in <module>
                      from baz import b
                    File "baz\b.py", line 1, in <module>
                      from baz import a
                  ImportError: cannot import name a
                  

                  类似的问题...但显然 from x import y 与 import import x.y as y 不同

                  例子4

                  qux/
                     __init__.py
                     a.py
                     b.py
                  
                  a.py:
                  import b 
                  
                  b.py:
                  import a
                  
                  >>> import qux.a
                  >>>
                  

                  这个也可以

                  【讨论】:

                  • 惊人的有趣事实!你能解释一下这背后的原因吗?
                  • 好问题!我不知道我脑海中的答案,但这听起来像是探索 Python 内部结构的有趣练习。
                  【解决方案17】:

                  这是我当前目录的目录结构:

                  .  
                  └─a  
                     └─b  
                       └─c
                  
                  1. import 语句会记住所有中间名
                    这些名称必须限定:

                    In[1]: import a.b.c
                    
                    In[2]: a
                    Out[2]: <module 'a' (namespace)>
                    
                    In[3]: a.b
                    Out[3]: <module 'a.b' (namespace)>
                    
                    In[4]: a.b.c
                    Out[4]: <module 'a.b.c' (namespace)>
                    
                  2. from ... import ... 语句只记住导入的名称
                    此名称不得限定:

                    In[1]: from a.b import c
                    
                    In[2]: a
                    NameError: name 'a' is not defined
                    
                    In[2]: a.b
                    NameError: name 'a' is not defined
                    
                    In[3]: a.b.c
                    NameError: name 'a' is not defined
                    
                    In[4]: c
                    Out[4]: <module 'a.b.c' (namespace)>
                    

                  • 注意:当然,我在第 1 步和第 2 步之间重新启动了 Python 控制台。

                  【讨论】:

                    【解决方案18】:

                    正如Jan Wrobel所提到的,不同进口的一个方面是进口的披露方式。

                    模块 mymath

                    from math import gcd
                    ...
                    

                    mymath的使用:

                    import mymath
                    mymath.gcd(30, 42)  # will work though maybe not expected
                    

                    如果我导入gcd 仅供内部使用,不向mymath 的用户透露,这可能会很不方便。我经常遇到这种情况,在大多数情况下,我想“保持我的模块干净”。

                    除了 Jan Wrobel 建议使用 import math 来掩盖这一点之外,我已经开始使用前导下划线隐藏导入:

                    # for instance...
                    from math import gcd as _gcd
                    # or...
                    import math as _math
                    

                    在较大的项目中,这种“最佳实践”允许我准确控制向后续导入披露的内容和不披露的内容。这使我的模块保持清洁并在一定规模的项目中得到回报。

                    【讨论】:

                      【解决方案19】:

                      因为很多人在这里回答,但我只是尽力而为:)

                      1. import module 最好在您不知道必须从 module 导入哪个项目时使用。这样在出现问题时可能很难调试,因为 你不知道哪个项目有问题。

                      2. form module import &lt;foo&gt; 最好在您知道需要导入哪个项目时使用,并且有助于根据需要使用导入特定项目进行更多控制。使用这种方式调试可能很容易,因为您知道自己导入了哪个项目。

                      【讨论】:

                        【解决方案20】:

                        我正在回答一个类似的问题帖子,但发帖人在我发布之前将其删除。这是一个说明差异的示例。

                        Python 库可能有一个或多个文件(模块)。例如,

                        package1
                          |-- __init__.py
                        

                        package2
                          |-- __init__.py
                          |-- module1.py
                          |-- module2.py
                        

                        我们可以根据设计要求在任何文件中定义python函数或类。

                        让我们定义

                        1. func1()__init__.pymylibrary1,和
                        2. foo()module2.pymylibrary2

                        我们可以使用其中一种方法访问func1()

                        import package1
                        
                        package1.func1()
                        

                        import package1 as my
                        
                        my.func1()
                        

                        from package1 import func1
                        
                        func1()
                        

                        from package1 import *
                        
                        func1()
                        

                        我们可以使用其中一种方法来访问foo()

                        import package2.module2
                        
                        package2.module2.foo()
                        

                        import package2.module2 as mod2
                        
                        mod2.foo()
                        

                        from package2 import module2
                        
                        module2.foo()
                        

                        from package2 import module2 as mod2
                        
                        mod2.foo()
                        

                        from package2.module2 import *
                        
                        foo()
                        

                        【讨论】:

                          【解决方案21】:

                          有很多答案,但没有一个提到测试(unittestpytest)。

                          tl;博士

                          import foo 用于外部模块以简化测试。

                          艰难的道路

                          从模块中单独导入类/函数 (from foo import bar) 会使红绿重构周期变得乏味。例如,如果我的文件看起来像

                          # my_module.py
                          
                          from foo import bar
                          
                          
                          class Thing:
                              def do_thing(self):
                                  bar('do a thing')
                          

                          我的测试是

                          # test_my_module.py
                          
                          from unittest.mock import patch
                          import my_module
                          
                          
                          patch.object(my_module, 'bar')
                          def test_do_thing(mock_bar):
                              my_module.Thing().do_thing()
                              mock_bar.assert_called_with('do a thing')
                          

                          乍一看,这似乎很棒。但是如果我想在不同的文件中实现Thing 类会发生什么?我的结构必须像这样改变......

                          # my_module.py
                          
                          from tools import Thing
                          
                          
                          def do_thing():
                              Thing().do_thing()
                          
                          
                          # tools.py
                          
                          from foo import bar
                          
                          
                          class Thing:
                              def do_thing(self):
                                  bar('do a thing')
                          
                          
                          # test_my_module.py
                          
                          from unittest.mock import patch
                          import my_module
                          import tools  # Had to import implementation file...
                          
                          
                          patch.object(tools, 'bar')  # Changed patch
                          def test_do_thing(mock_bar):
                              my_module.do_thing()  # Changed test (expected)
                              mock_bar.assert_called_with('do a thing')
                          

                          不幸的是,由于我使用了from foo import bar,我需要更新我的补丁以引用tools 模块。从本质上讲,由于我的测试对实现了解太多,因此需要进行比预期更多的更改才能进行此重构。

                          更好的方法

                          使用import foo,我的测试可以忽略模块是如何实现的,只需修补整个模块。

                          # my_module.py
                          
                          from tools import Thing
                          
                          
                          def do_thing():
                              Thing().do_thing()
                          
                          
                          # tools.py
                          
                          import foo
                          
                          
                          class Thing:
                              def do_thing(self):
                                  foo.bar('do a thing')  # Specify 'bar' is from 'foo' module
                          
                          
                          # test_my_module.py
                          
                          from unittest.mock import patch
                          import my_module
                          
                          
                          patch('foo')  # Patch entire foo module
                          def test_do_thing(mock_foo):
                              my_module.do_thing()  # Changed test (expected)
                              mock_foo.bar.assert_called_with('do a thing')
                          

                          您的测试知道的实现细节越少越好。这样一来,如果您想出更好的解决方案(使用类而不是函数,使用额外的文件来分隔想法等),则需要在测试中进行较少的更改以适应重构。

                          【讨论】:

                            猜你喜欢
                            • 1970-01-01
                            • 2021-12-08
                            • 1970-01-01
                            • 1970-01-01
                            • 1970-01-01
                            • 2017-12-12
                            • 2015-10-06
                            • 1970-01-01
                            相关资源
                            最近更新 更多