导入模块'或者从模块导入'?

我试图找到一个关于是否最好使用import modulefrom module import的全面指南。我刚刚开始学习Python,我正试图从最佳实践开始。

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

231006 次浏览

支持这两种方法是有原因的:有时一种比另一种更合适。

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

  • from module import ...:导入的项目可以直接使用,没有模块名前缀。缺点是必须列出所使用的每一个东西,并且在代码中不清楚某些东西是从哪里来的。

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

import modulefrom module import foo之间的区别主要是主观的。选择一个你最喜欢的,并始终如一地使用它。这里有几点可以帮助你做决定。

# EYZ0

  • < >强优点:
    • 减少对import语句的维护。不需要添加任何额外的导入就可以开始使用模块中的另一个项
    • 李< / ul > < / >
    • < >强缺点:
      • 在代码中输入module.foo可能是乏味和冗余的(使用import module as mo然后输入mo.foo可以将单调最小化)
      • 李< / ul > < / >

      # EYZ0

      • < >强优点:
        • 少输入使用foo
        • 对可以访问模块的哪些项有更多的控制
        • 李< / ul > < / >
        • < >强缺点:
          • 要使用模块中的新项,必须更新import语句
          • 你失去了关于foo的上下文。例如,与math.ceil()相比,ceil()做什么就不太清楚了
          • 李< / ul > < / >

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

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

import module

当您将使用模块中的许多函数时是最好的。

from module import function

当您只需要function而希望避免使用模块中的所有函数和类型污染全局名称空间时,使用该选项是最好的。

我个人总是用

from package.subpackage.subsubpackage import module

然后访问所有

module.function
module.modulevar

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

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

当然,如果在两个不同的包中对两个不同的模块使用相同的模块名,您可能会遇到麻烦,例如

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

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

为了补充人们对from x import *的评论:除了使名称的来源更加困难之外,这还会使Pylint等代码检查器无法使用。它们将报告这些名称为未定义的变量。

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

通常我更喜欢简单导入的自文档风格,只更改from..当我必须输入模块名的次数增加到10到20次时,即使只有一个模块被导入。

我刚刚发现这两种方法之间还有一个微妙的区别。

如果模块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)。当不再需要导入并删除时,出现问题的模块就被破坏了。

这里还有另一个细节,没有提到,与写入模块有关。虽然这可能不太常见,但我时不时地需要它。

由于Python中引用和名称绑定的工作方式,如果你想更新模块中的某个符号,请输入foo。条,从该模块之外,并有其他导入代码"你必须以某种方式导入foo。例如:

模块foo:

bar = "apples"

模块一:

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

模块2:

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的设置仅仅影响了名字“bar”;在模块a中,它没有“到达”;foo模块对象,并更新它的bar

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

注意,当使用

from package import item

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

相反,当使用语法像

import item.subitem.subsubitem

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

尽管许多人已经解释了importimport from的区别,但我想尝试更多地解释一下底层发生了什么,以及它所改变的所有地方。


# EYZ0:

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

例如:foo.bar,而不是bar

# EYZ0:

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

例如:bar,而不是bazfoo.baz

# EYZ0:

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

例如,barbaz,但不是_quxfoo._qux


现在让我们看看当我们执行import X.Y时:

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

检查名称osos.pathsys.modules:

>>> 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:

# EYZ0:

>>> 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()中的“path”:

>>> 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'
>>>
import package
import module

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

当有子包时:

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

对文件夹(包)或文件(模块)的要求是相同的,但文件夹或文件必须在package2中,而package2必须在package1中,并且package1package2都必须包含__init__.py文件。# EYZ5

使用from样式导入:

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

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

a = big_package_name.subpackage.even_longer_subpackage_name.function

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

from package3.module import some_function

是允许的,但是

import package3.module.some_function

是不允许的。

导入模块——你不需要额外的努力从模块中获取另一个东西。它有缺点,如冗余输入

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

因为我也是一个初学者,我将试图用一种简单的方式解释这一点: 在Python中,我们有三种类型的import语句:

1. 一般进口:

import math

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

math.sqrt(4)

当然,它增加了输入工作,但作为初学者,它将帮助您跟踪与之相关的模块和函数(一个好的文本编辑器将大大减少输入工作,推荐使用)。

输入工作可以通过使用import语句进一步减少

import math as m

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

2. 功能导入:

from math import sqrt

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

3.普遍的进口:

from math import *

虽然它大大减少了输入工作,但不推荐,因为它将用模块中的各种函数填充代码,并且它们的名称可能与用户定义函数的名称冲突。 例子:< / >强

如果你有一个自己命名为sqrt的函数,并且你导入了math,你的函数是安全的:这是你的sqrt,这是math.sqrt。但是,如果从math import *执行,则会遇到一个问题:即,两个不同的函数具有完全相同的名称。来源:# EYZ0

有一些内置模块主要包含裸函数(base64数学操作系统shutilsys时间,…),将这些裸函数绑定放到某个命名空间中绝对是一个很好的实践,从而提高代码的可读性。想想看,如果没有命名空间,要理解这些函数的含义是多么困难:

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

当它们被绑定到某个模块时:

math.copysign(foo, bar)
time.monotonic()
shutil.copystat(foo, bar)
有时你甚至需要命名空间来避免不同模块之间的冲突(json.load vs. pickle.load)
另一方面,有些模块包含大部分类(configparserdatetimetempfilezipfile,…),其中许多模块的类名不言自明:

configparser.RawConfigParser()
datetime.DateTime()
email.message.EmailMessage()
tempfile.NamedTemporaryFile()
zipfile.ZipFile()

因此,在代码中使用这些类和额外的模块名称空间是增加了一些新信息还是只是延长了代码,可能会有争论。

我发现令人惊讶的是没有人谈论的一个重要区别是,使用普通的进口,你可以从导入的模块中访问private variableprivate functions,这是用进口语句无法实现的。

enter image description here

图像中的代码:

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

我还想补充一点。如果遇到循环导入,了解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在每个例子中都是result),但如果你需要考虑以下四个循环导入,这是值得注意的:

例二

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

这是

example2

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'

不行

青年们

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

类似的问题……但显然,从x导入y并不等于从y导入x

example4

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

这个也可以

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

.
└─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. The from ... import ... statement remembers only the imported name.
    This name must not be qualified:

    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)>
    

  • Note: Of course, I restarted my Python console between steps 1 and 2.

正如1月“所提到的,不同导入的一个方面是导入的公开方式。

模块# EYZ0

from math import gcd
...

使用mymath:

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

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

除了建议使用1月“来使用import math来掩盖这一点之外,我已经开始使用前导下划线来隐藏导入,以避免公开:

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

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

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

    当你不知道你必须从module导入哪个项目时,import module是最好的。通过这种方式,当问题出现时可能很难进行调试,因为

    .你不知道哪个项目有问题
  1. form module import <foo>是最好的,当你知道你需要导入哪个项目时,也有助于更好地控制根据你的需要导入特定的项目。使用这种方式调试可能很容易,因为您知道导入了哪个项。

我正在回答一个类似的问题,但在我发布之前,发帖者删除了它。这里有一个例子来说明这些区别。

Python库可以有一个或多个文件(模块)。为例子,

package1
|-- __init__.py

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

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

让我们来定义

  1. func1()mylibrary1下的__init__.py中,以及
  2. foo()mylibrary2下的module2.py中。

我们可以使用这些方法之一访问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()

有很多答案,但没有一个提到测试(用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')

测试知道的实现细节越少越好。这样,如果您提出了更好的解决方案(使用类而不是函数,使用额外的文件来分离思想,等等),那么在您的测试中需要更改的内容就会更少,以适应重构。

简单地说,这都是为了方便程序员。在核心级别,它们只是导入模块的所有功能。

import module:当你使用import module时,然后使用这个模块的方法,你必须写module.method()。每次使用任何方法或属性时,都必须引用模块。

from module import all:当你使用from module import all来使用这个模块的方法时,你只需要写method()而不需要引用模块。

@ahfx已经提到了这些导入的一个关键方面,即加载模块过程的内部。如果你的系统需要使用循环导入(例如,你想在一些流行的http框架中使用依赖注入),就会弹出这个窗口。在这种情况下,from {module} import {function}对于加载过程如何进行的要求显得更加激进。让我们举个例子:

#m1.py:
print('--start-m1--')
from m2 import *    # form does not matter; just need to force import of m2
print('--mid-m1--')


def do1(x):
print(x)


print('--end-m1--')

进口

#m2.py
print('--start-m2--')


# from m1 import *      # A
# from m1 import do1    # B
# import m1             # C
# D -- no import of "do1" at all
                        

print('--mid-m2--')


def do2(x):
m1.do1(x)


print('--end-m2--')

通过运行

#main.py:
from m1 import do1
do1('ok')

m2.py (A,B,C,D)中所有的导入可能性中,from {module} import {function}唯一的一个,它实际上崩溃了加载过程,导致了臭名昭著的(CPython 3.10.6)

ImportError: cannot import name 'do1' from partially initialized module 'm1'
(most likely due to a circular import)

虽然我不能说为什么会这样,但似乎from ... import ...声明对“多远”提出了更严格的要求;所讨论的模块已经处于初始化过程中。