我试图找到一个全面的指南,是否最好使用导入模块或从模块导入。我刚刚开始学习Python,我正试图从最佳实践开始。

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


当前回答

import module

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

from module import function

如果只需要函数,而希望避免使用模块中的所有函数和类型污染全局名称空间,则最好使用此方法。

其他回答

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

虽然我不能说为什么会发生这种情况,但似乎从……进口…语句对有问题的模块已经处于初始化过程的“多远”提出了更严格的要求。

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

导入模块

优点: 更少地维护您的导入语句。不需要添加任何额外的导入就可以开始使用模块中的另一个项 缺点: 输入模块。Foo在你的代码中可能是乏味和冗余的(单调可以通过使用import module作为mo然后输入mo. Foo来最小化)

从模块导入foo

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

这两种方法都可以,但不要使用from module import *。

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

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

由于Python中引用和名称绑定的工作方式,如果你想更新模块中的某个符号,请输入foo。Bar,从模块外部,并有其他导入代码“看到”的变化,你必须以某种方式导入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"

没有代码在一个外部将看到bar作为“橙子”,因为我的bar设置只是影响模块a中的名称“bar”,它没有“到达”foo模块对象并更新它的bar。

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

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

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

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

有时你甚至需要命名空间来避免不同模块之间的冲突(json. xml)。Load vs. pickle.load) 另一方面,有些模块包含大部分类(configparser, datetime, tempfile, zipfile,…),其中许多模块的类名不言自明:

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

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

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

导入模块:当你从模块中使用很多位的时候很好。缺点是需要用模块名限定每个引用。 从模块导入…:导入的项目可以直接使用,不需要模块名前缀。缺点是必须列出所使用的每一个东西,并且在代码中不清楚某些东西是从哪里来的。

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