關於import,你應該知道這些內容

2021-02-20 編程派

編程派微信號:codingpy

本文為原創編譯,首發於微信公眾號「編程派」。微信搜索「編程派」,獲取更多Python編程教程和精彩資源吧!

原文:Python 101 - All about imports

文中藍色下劃線部分為外鏈,請點擊閱讀原文查看相關連結的內容。

作為一名新手Python程式設計師,你首先需要學習的內容之一就是如何導入模塊或包。但是我注意到,那些許多年來不時使用Python的人並不是都知道Python的導入機制其實非常靈活。

在本文中,我們將探討以下話題:

常規導入(regular imports)

使用from語句導入

相對導入(relative imports)

可選導入(optional imports)

本地導入(local imports)

導入注意事項



常規導入應該是最常使用的導入方式,大概是這樣的:

import sys

你只需要使用import一詞,然後指定你希望導入的模塊或包即可。通過這種方式導入的好處是可以一次性導入多個包或模塊:

import os, sys, time

雖然這節省了空間,但是卻違背了Python風格指南。Python風格指南建議將每個導入語句單獨成行

有時在導入模塊時,你想要重命名這個模塊。這個功能很容易實現:

import sys as system

print(system.platform)

上面的代碼將我們導入的sys模塊重命名為system。我們可以按照和以前一樣的方式調用模塊的方法,但是可以用一個新的模塊名。也有某些子模塊必須要使用點標記法才能導入。

import urllib.error

這個情況不常見,但是對此有所了解總是沒有壞處的。


很多時候你只想要導入一個模塊或庫中的某個部分。我們來看看在Python中如何實現這點:

from functools import lru_cache

上面這行代碼可以讓你直接調用lru_cache。如果你按常規方式導入functools,那麼你就必須像這樣調用lru_cache:

functools.lru_cache(*args)

根據你實際的使用場景,上面的做法可能是更好的。在複雜的代碼庫中,能夠看出某個函數是從哪裡導入的這點很有用的。不過,如果你的代碼維護的很好,模塊化程度高,那麼只從某個模塊中導入一部分內容也是非常方便和簡潔的。

當然,你還可以使用from方法導入模塊的全部內容,就像這樣:

from os import *

這種做法在少數情況下是挺方便的,但是這樣也會打亂你的命名空間。問題在於,你可能定義了一個與導入模塊中名稱相同的變量或函數,這時如果你試圖使用os模塊中的同名變量或函數,實際使用的將是你自己定義的內容。因此,你最後可能會碰到一個相當讓人困惑的邏輯錯誤。標準庫中我唯一推薦全盤導入的模塊只有Tkinter。

如果你正好要寫自己的模塊或包,有人會建議你在__init__.py文件中導入所有內容,讓模塊或者包使用起來更方便。我個人更喜歡顯示地導入,而非隱式地導入。

你也可以採取折中方案,從一個包中導入多個項:

from os import path, walk, unlink

from os import uname, remove

在上述代碼中,我們從os模塊中導入了5個函數。你可能注意到了,我們是通過多次從同一個模塊中導入實現的。當然,如果你願意的話,你也可以使用圓括號一次性導入多個項:

from os import (path, walk, unlink, uname,                remove, rename)

這是一個有用的技巧,不過你也可以換一種方式:

from os import path, walk, unlink, uname, \                remove, rename

上面的反斜槓是Python中的續行符,告訴解釋器這行代碼延續至下一行。


PEP 328介紹了引入相對導入的原因,以及選擇了哪種語法。具體來說,是使用句點來決定如何相對導入其他包或模塊。這麼做的原因是為了避免偶然情況下導入標準庫中的模塊產生衝突。這裡我們以PEP 328中給出的文件夾結構為例,看看相對導入是如何工作的:

my_package/    __init__.py    subpackage1/        __init__.py        module_x.py        module_y.py    subpackage2/        __init__.py        module_z.py    module_a.py

在本地磁碟上找個地方創建上述文件和文件夾。在頂層的__init__.py文件中,輸入以下代碼:

from . import subpackage1

from . import subpackage2

接下來進入subpackage1文件夾,編輯其中的__init__.py文件,輸入以下代碼:

from . import module_x

from . import module_y

現在編輯module_x.py文件,輸入以下代碼:

from .module_y import spam as ham
def main():    ham()

最後編輯module_y.py文件,輸入以下代碼:

def spam():    print('spam ' * 3)

打開終端,cd至my_package包所在的文件夾,但不要進入mu_package。在這個文件夾下運行Python解釋器。我使用的是IPython,因為它的自動補全功能非常方便:

In [1]: import my_package

In [2]: my_package.subpackage1.module_x

Out[2]: <module 'my_package.subpackage1.module_x' from 'my_package/subpackage1/module_x.py'>

In [3]: my_package.subpackage1.module_x.main()

spam spam spam

相對導入適用於你最終要放入包中的代碼。如果你編寫了很多相關性強的代碼,那麼應該採用這種導入方式。你會發現PyPI上有很多流行的包也是採用了相對導入。還要注意一點,如果你想要跨越多個文件層級進行導入,只需要使用多個句點即可。不過,PEP 328建議相對導入的層級不要超過兩層。

還要注意一點,如果你往module_x.py文件中添加了if __name__ == 『__main__』,然後試圖運行這個文件,你會碰到一個很難理解的錯誤。編輯一下文件,試試看吧!

from . module_y import spam as ham

def main():

   ham()

if __name__ == '__main__':    

# This won't work!

   main()

現在從終端進入subpackage1文件夾,執行以下命令:

python module_x.py

如果你使用的是Python 2,你應該會看到下面的錯誤信息:

Traceback (most recent call last):

  File "module_x.py", line 1, in <module>

    from . module_y import spam as ham

ValueError: Attempted relative import in non-package

ValueError: Attempted relative import in non-package

如果你使用的是Python 3,錯誤信息大概是這樣的:

Traceback (most recent call last):

  File "module_x.py", line 1, in <module>

    from . module_y import spam as ham

SystemError: Parent module '' not loaded, cannot perform relative import

SystemError: Parent module '' not loaded, cannot perform relative import

這指的是,module_x.py是某個包中的一個模塊,而你試圖以腳本模式執行,但是這種模式不支持相對導入

如果你想在自己的代碼中使用這個模塊,那麼你必須將其添加至Python的導入檢索路徑(import search path)。最簡單的做法如下:

import sys

sys.path.append('/path/to/folder/containing/my_package')

import my_package

注意,你需要添加的是my_package的上一層文件夾路徑,而不是my_package本身。原因是my_package就是我們想要使用的包,所以如果你添加它的路徑,那麼將無法使用這個包。

我們接下來談談可選導入。


如果你希望優先使用某個模塊或包,但是同時也想在沒有這個模塊或包的情況下有備選,你就可以使用可選導入這種方式。這樣做可以導入支持某個軟體的多種版本或者實現性能提升。以github2包中的代碼為例:

try:    

   from http.client import responses

except ImportError:  

   try: 

       from httplib import responses  

   except ImportError:          from BaseHTTPServer import BaseHTTPRequestHandler as _BHRH        responses = dict([(k, v[0]) for k, v in _BHRH.responses.items()])

lxml包也有使用可選導入方式:

try:

    from urlparse import urljoin

    from urllib2 import urlopen

except ImportError:    

    from urllib.parse import urljoin 

    from urllib.request import urlopen

正如以上示例所示,可選導入的使用很常見,是一個值得掌握的技巧。


當你在局部作用域中導入模塊時,你執行的就是局部導入。如果你在Python腳本文件的頂部導入一個模塊,那麼你就是在將該模塊導入至全局作用域,這意味著之後的任何函數或方法都可能訪問該模塊。例如:

import sys  

def square_root(a):

   

   import math 

   return math.sqrt(a)

def my_pow(base_num, power):

   return math.pow(base_num, power)

if __name__ == '__main__':

   print(square_root(49))    print(my_pow(2, 3))

這裡,我們將sys模塊導入至全局作用域,但我們並沒有使用這個模塊。然後,在square_root函數中,我們將math模塊導入至該函數的局部作用域,這意味著math模塊只能在square_root函數內部使用。如果我們試圖在my_pow函數中使用math,會引發NameError。試著執行這個腳本,看看會發生什麼。

使用局部作用域的好處之一,是你使用的模塊可能需要很長時間才能導入,如果是這樣的話,將其放在某個不經常調用的函數中或許更加合理,而不是直接在全局作用域中導入。老實說,我幾乎從沒有使用過局部導入,主要是因為如果模塊內部到處都有導入語句,會很難分辨出這樣做的原因和用途。根據約定,所有的導入語句都應該位於模塊的頂部。


在導入模塊方面,有幾個程式設計師常犯的錯誤。這裡我們介紹兩個。

先來看看循環導入。


如果你創建兩個模塊,二者相互導入對方,那麼就會出現循環導入。例如:

# a.py

import b

def a_test():

   print("in a_test")    b.b_test()a_test()

然後在同個文件夾中創建另一個模塊,將其命名為b.py。

import a

def b_test():

   print('In test_b"')    a.a_test()b_test()

如果你運行任意一個模塊,都會引發AttributeError。這是因為這兩個模塊都在試圖導入對方。簡單來說,模塊a想要導入模塊b,但是因為模塊b也在試圖導入模塊a(這時正在執行),模塊a將無法完成模塊b的導入。我看過一些解決這個問題的破解方法(hack),但是一般來說,你應該做的是重構代碼,避免發生這種情況。


當你創建的模塊與標準庫中的模塊同名時,如果你導入這個模塊,就會出現覆蓋導入。舉個例子,創建一個名叫math.py的文件,在其中寫入如下代碼:

import math

def square_root(number):

   return math.sqrt(number)square_root(72)

現在打開終端,試著運行這個文件,你會得到以下回溯信息(traceback):

Traceback (most recent call last):

  File "math.py", line 1, in <module>

    import math

  File "/Users/michael/Desktop/math.py", line 6, in <module>

   square_root(72)

  File "/Users/michael/Desktop/math.py", line 4, in square_root    return math.sqrt(number)

 AttributeError: module 'math' has no attribute 'sqrt'

   square_root(72) 

 File "/Users/michael/Desktop/math.py", line 4, in square_root

    return math.sqrt(number)

AttributeError: module 'math' has no attribute 'sqrt'

這到底是怎麼回事?其實,你運行這個文件的時候,Python解釋器首先在當前運行腳本所處的的文件夾中查找名叫math的模塊。在這個例子中,解釋器找到了我們正在執行的模塊,試圖導入它。但是我們的模塊中並沒有叫sqrt的函數或屬性,所以就拋出了AttributeError。


在本文中,我們講了很多有關導入的內容,但是還有部分內容沒有涉及。PEP 302中介紹了導入鉤子(import hooks),支持實現一些非常酷的功能,比如說直接從github導入。Python標準庫中還有一個importlib模塊,值得查看學習。當然,你還可以多看看別人寫的代碼,不斷挖掘更多好用的妙招。


Import traps
Circular imports in Python 2 and Python 3
Stackoverflow – Python relative imports for the billionth time


相關焦點

  • python基礎--自定義模塊、import、from......import......
    如果你退出python解釋器然後重新進入,那麼你之前定義的函數或者變量都將丟失,因此我們通常將程序寫到文件中以便永久保存下來,需要時就通過python test.py方式去執行,此時test.py被稱為腳本script。所以,腳本就是一個python文件,比如你之前寫的購物車,模擬博客園登錄系統的文件等等。3.模塊的分類Python語言中,模塊分為三類。
  • 【Python】關於 Type Hints 你應該知道這些(1/2)
    本文試著對 Typing 模塊進行一次簡要的介紹,拋磚引玉,希望能對你有所幫助。在我們開始 Typing 模塊之旅前,需要了解一些關於Type System 的相關知識。在使用Python時,我們雖然也知道每個對象有類型,但不需要特別擔心它的限制,因為Python的自由和寬容,使我們只要專注在待解決的問題上。這正是 Python 高效開發的優勢,不過也會產生較多隨意的代碼,尤其在大型的項目中,由多人參與完成,可讀性往往是一個很重要的指標,它決定代碼的可復用性,可維護性甚至代碼的壽命。
  • 深入探討 Python 的 import 機制
    也許你看到這個標題,會說我怎麼會發這麼基礎的文章?與此相反。恰恰我覺得這篇文章的內容可以算是 Python 的進階技能,會深入地探討並以真實案例講解 Python import Hook 的知識點。當然為了使文章更系統、全面,前面會有小篇幅講解基礎知識點,但請你有耐心的往後讀下去,因為後面才是本篇文章的精華所在,希望你不要錯過。
  • 詳解Python中的import的用法
    故事是從這篇臺灣同胞的博客《Python的import陷阱》[1](網址見底部)開始的,然後又跳到了Python社區的PEP 328提案[2],再結合過去的經驗以及一些測試,我想我大概懂了吧。下面是我的總結,希望內容能夠言簡意賅、易於理解。import語句有什麼用?
  • Python Import 機制與拓展——劉暢@PyCon 2015 China
    在真正的將python應用到實際的項目中,你會遇到一些無法避免的問題。最讓人困惑不解的問題有二類,一個 編碼問題,另一個則是引用問題。本文主要討論關於Python中import的機制、實現、以及介紹一些有意思的Python Hooks。
  • 關於About和On,你應該了解這些內容!
    這些同義、近義詞語的存在,既給英語增輝添彩,避免枯燥、單調和乏味,也給英語學習者帶來不少困難。一.單詞釋義[1] about①prep(介詞). 關於;大約。②adj(形容詞). 四處走動的;在起作用的,在附近的。③adv(副詞). 大約;到處;周圍。④n(名詞). 大致;粗枝大葉;不拘小節的人。
  • 徹底搞懂Python 中的 import 與 from import
    但是,如果你使用from re import search, sub, S, I來寫代碼,那麼代碼就會變成這樣:import research('c(.*?)但是如果你寫為from datetime import datetime,那麼你導入的datetime是一個type類:
  • 關於react結合redux使用,或許你還應該掌握這些
    reactreact概念首先,我們需要知道的是:React是一個聲明式的,高效且靈活的用於構建用戶界面的javascript庫。React可以將一些簡短,獨立的代碼片段(亦稱『組件』)組合成複雜的UI界面。注意:react是一個庫,而不是框架。拓展:庫和框架有什麼區別?關於庫:庫(Lab)是將代碼集合成的一個產品,以供研發人員調用。
  • 詳解Python import機制(一):import中的基本概念
    「模塊」:一個以「.py」為後綴的文件就是一個模塊「常規包」:「__init__.py」所在目錄就是一個常規包「命名空間包」:命名空間包是一種虛擬的概念,它由多個子包構成,這些子包可以在任意位置,可以為 zip 中的文件或網絡上的文件,這些子包在概念上是一個整體,這個整體就是一個命名空間包「常規包」與「命名空間包」的概念在 PEP420 被提出,在 Python3.3 及之後的
  • SV中import和include的區別
    一個常見問題:我們應該從package中import類或者Include它們?為了正確回答這個問題,先主要介紹這兩個關鍵詞的區別。
  • 詳解Python import機制(上):import中的基本概念
    import其實有很多容易混淆的概念以及可以實現很多非常有趣的玩法,本篇文章拋磚引玉,聊聊import需注意,Python2與Python3的import機制有較大差別,主要體現在兩個節點,Python2.6之前使用relative import(相對導入)作為默認import機制,Python2.6之後使用absolute import(絕對導入)作為默認import機制
  • Python 中的 import 與 from import 到底啥意思?
    但是,如果你使用from re import search, sub, S, I來寫代碼,那麼代碼就會變成這樣:import research('c(.*?)但是如果你寫為from datetime import datetime,那麼你導入的datetime是一個type類:
  • Python入門基礎之導入問題:from import 與import 詳解
    主要有如下作用:代碼重用:我們知道當一段代碼需要用到兩次的時候,我們就需要寫一個函數了這是一個道理。避免變量名的衝突:每個模塊都將變量名封裝進了自己包含的軟體包,這可以避免變量名的衝突。除非使用精確導入。
  • 關於夏令營的內情 你應該知道這些
    關於夏令營的那些事 這些你一定要知道 百花之中也有糟粕,怎麼去偽純真,怎麼選擇合適的下面這些,你一定要看看。希望能幫到你! 再次,夏令營的價格虛高,就是因為夏令營處於旅遊和教育培訓之間的一個交叉市場,在各項財務數據正常情況下,遊學的利潤也應該是介於旅遊和教育培訓之間的。舉例說明:運營良好的旅遊公司純利3%-5%;運營良好的教育機構純利大概將近20%;那麼,介於旅遊和教育之間的「遊學」公司,純利多數在這兩者中間,那麼關於定價的整體標準又出現了問題!
  • 作為數據科學家你應該知道這些 python 多線程、進程知識
    其中,最重要的是下面的這些問題。競爭條件:正如我們已經討論過的,線程有一個共享內存空間,因此它們可以訪問共享變量。當多個線程試圖同時更改同一個變量時,會出現競爭條件。線程調度程序可以在線程之間任意交換,因此我們無法知道線程嘗試更改數據的順序。這可能會導致兩個線程中的任何一個出現不正確的行為,特別是當線程決定基於變量的值執行某些操作時。
  • 學了半天,import 到底在幹啥?
    我們可以導入sys查看一下這個對象的具體內容(節省篇幅,做省略處理):>>> import 畢竟資源是有限的,Python不可能把你可能用到的所有模塊全都一股腦給加載起來,否則這樣男上加男加男加男……誰也頂不住啊不是(大霧
  • 初窺 Python 的 import 機制
    在加載器中,你完全可以決定如何來加載以及執行一個模塊。實際上你可以添加一個勾子來改變 sys.meta_path 或者 sys.path,從而來改變 import 機制的行為。上面的例子中,我們直接修改了 sys.meta_path。
  • 你對Python裡的import一無所知
    from ... import ... as ...一般情況下,使用 import 語句導入模塊已經夠用的。但是在一些特殊場景中,可能還需要其他的導入方式。下面我會一一地給你介紹。2. 使用 __import__ __import__ 函數可用於導入模塊,import 語句也會調用函數。
  • 關於社交,這些是你最應該知道的
    繼續釋放一下,可以得到以下公式:個性(優勢)+興趣(需求)+時間付出=回報A端是每個人都會有的不同個性,這些會是他的優勢,個性的優勢就跟實體交易過程中的資產類似,是你的籌碼。比如我很帥,我很美,我才華橫溢,我幽默可人等等。
  • Python模塊import本質是什麼?
    如果你寫過自定義的模塊或包,你應該會發現import只會在第一次發生,如果修改代碼需要通過reload來強制加載模塊,這其中可以理解為Python在import的時候進行了動態加載機制將模塊加載到內存當中,我們可以通過sys.modules來查看當前執行環境的內存中已經存在的模塊,那如果理解成只要在sys.modules中已經加載過的模塊是否就不需要import了呢?