我們在學 python的時候,大多數都是從
print("hello,world")
開始,這一行代碼,敲開了每一位工程師新世界的大門!
然後我們開始學文法、變量、函數、條件控制、資料結構、面向對象,然後迫不及待的與bug過招。在這個過程中,有一位朋友一直默默陪伴着我們,但是我們卻從來沒有關注過它。他就是我們的
import
兄弟
import os
在我們的代碼中,它扮演這不可或缺的角色。但是卻很少有人真正懂它。正是因為有了他,python的強大之處才能得以發揮,今天讓我們一起好好了解一下它。
1. 子產品化程式設計
在我們真實的項目中,代碼量可能達到幾十萬、幾百萬行。如果我們把這幾十萬、幾百萬行代碼都寫在一個檔案裡面。那後果是非常嚴重的,首先我們的代碼檔案将會非常大,其次是想通過肉眼去找到我們的代碼,難度也是非常大,更别說看懂其中的代碼邏輯了。
出現了子產品化程式設計的想法。子產品化程式設計有助于開發者統籌兼顧和分工協作,并提升代碼靈活性和可維護性。比如說衆多工程師共同開發同一個系統。A做登入功能的邏輯,B做注冊功能的邏輯,C做使用者管理的邏輯。這些代碼是分離的,通過子產品化組裝的方式把他繼承到同一個系統中。
子產品化程式設計:将一個完整系統的代碼,拆分成一個一個小子產品
舉個例子:
(1)非子產品化項目:項目裡面隻有一個py檔案 main.py
def login():
#這裡完成登入邏輯的代碼編輯
pass
def register():
# 這裡完成注冊邏輯的代碼編寫
pass
def user_manage():
# 這裡完成使用者管理的代碼編寫
pass
if __main__ == "__main__":
login()
register()
user_manage()
(2)子產品化的項目:項目中包含
main.py
、
login.py
、
register.py
、
user_manage.py
- login.py
def login(): #這裡完成登入邏輯的代碼編輯 pass
- register.py
def register(): # 這裡完成注冊邏輯的代碼編寫 pass
- user_manage.py
def user_manage(): # 這裡完成使用者管理的代碼編寫 pass
- main.py
from login import login from register import register from user_manage import user_manage if __main__ == "__main__": login() register() user_manage()
其實,Python之是以這麼強大,這個特性發揮了很大的作用。我們需要的很多功能不用自己去寫,通過導入别人寫好的子產品,我們可以直接使用,這樣可以大大提供效率。比如我們想要寫一個爬蟲,假設沒有子產品化的助力,我們需要從0開始編寫,http請求、TCP連接配接、傳回處理、底層資料包封裝、解析。但是有了子產品化,我們隻需要一行代碼,開箱即用,是不是非常友善呢
import requests
2. Python中的子產品
說了子產品化程式設計,那麼在Python中,我們的子產品到底指的是什麼呢?在Python中,我們需要區分幾個概念:
- 子產品:一個字尾為
的代碼檔案就是一個子產品.py
- 包:一個包含很多
檔案的檔案夾。(Python3.3之前要求這個檔案夾中必須含有.py
檔案)__init__.py
- 庫:可能由多個包和子產品組成,可以認為是一個完整項目的打包。
3. import語句
3.1 從子產品導入
(1)全量導入:
全量導入會将子產品内的所有全局變量、函數、類等等全部都導入進來。
全量導入一個子產品的所有内容有兩種方式:
import xxx
import test
print(dir(test)
# 檢視導入了什麼
# 發現我們定義的Hello類、hello函數、name變量全部都被導入進來了
# 并且還有一些其他的東西
['Hello', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'hello', 'name']
test.hello()
print(test.name)
# 上面方法需求使用:子產品名.變量名(方法名)引用,因為隻是引入子產品整體,并沒有把子產品裡面的内容單獨引入
from xxx import *
from test import *
hello()
Hello()
print(name)
# 以上可以直接使用,因為 from test import * 已經将test子產品的所有内容單獨導入進來。
(2)局部導入
from xxx import xxx
from test import hello # 隻從test子產品中導入hello函數,别的不導入
from test import hello, Hello # 導入多個
hello()
給導入的内容設定别名:
from xxx import xxx as yyy
from test import hello as hello_func # 從test子產品中導入hello函數,并且設定别名為:hello_func
hello_func()
3.2 從python包中導入
|——test_package
|——__init__.py
|——test.py
|——test2.py
|——main.py
main.py:
大家猜猜,下面這兩段代碼能運作嗎?
import test_package
test_package.test.hello()
Traceback (most recent call last):
File "/app/util-python/python-module/main.py", line 8, in <module>
test_package.test.hello()
AttributeError: module 'test_package' has no attribute 'test'
from test_package import *
test.hello()
Traceback (most recent call last):
File "/app/util-python/python-module/main.py", line 10, in <module>
test.hello()
AttributeError: module 'test' has no attribute 'hello'
看到這裡,可能大家臉上都出現了三個問号???為什麼導入子產品的方法放在導入包這裡不好使了呢?
其實啊,我們的python在導入一個子產品的時候,會把我們的子產品
.py
檔案執行一遍,然後生成一個子產品對象,子產品中我們定義的函數、變量、類會添加到這個子產品對象的屬性裡面:這就是為什麼我們可以通過
test.hello()
,因為hello()是test的一個屬性.
那導入包的時候呢?我們知道python的包本質上是一個檔案夾,檔案夾是不能被編譯執行的。那為什麼還能import呢?實際上,我們在import一個包的時候,執行的是這個包裡面的
__init__.py
檔案,也可以了解為導入包的時候,隻是導入了
__init__.py
因為我們
__init__.py
是空的,是以我們導入了個寂寞。
(1)通過導入子產品的方式:
from test_package import test
test.hello()
from test_package.test import hello
hello()
(2)通過添加
__all__
屬性:
__init__.py
檔案: 添加
__all__
屬性
__all__ = ["test", "test2"]
main.py檔案:
from test_package import *
# 通過在 __init__.py中定義了 __all__屬性,在導入的時候,可以把該屬性列出的子產品全部導入
test.hello()
test2.hello2()
是針對子產品公開接口的一種約定,以提供了”白名單“的形式暴露接口。如果定義了
__all__
,其他檔案中使用
__all__
導入該檔案時,隻會導入
from xxx import *
列出的成員
__all__
僅對于使用
__all__
這種情況适用。
from module import *
4. 動态導入
上面我們介紹了比較主流的
import
語句導入,其實在python中還有其他的導入方式
4.1 __import__()
__import__()
__import__()
函數可用于導入子產品。其實當我們使用import導入Python子產品的時候,預設調用的是
__import__()
函數。直接使用該函數的情況很少見,一般用于動态加載子產品。
__import__(name, globals=None, locals=None, fromlist=(), level=0)
參數:
- name:要導入的子產品名,可使用變量
- globals和locals:通常使用預設值。使用給定的globals和locals變量來決定如何在一個包上下文中解析name
- fromlist:指定要導入的子子產品名或對象名,它們會按名稱從子產品導入
- leve:指定導入子產品的方式。level為0則絕對導入; level為正值則表示相對于調用__import __()的子產品目錄,要搜尋的父目錄數
os_obj = __import__("os")
print(os_obj.getcwd())
os_obj = __import__("test_package.test")
os_obj.test.hello()
4.2 importlib
importlib 是 Python 中的一個标準庫,importlib 能提供的功能非常全面
import importlib
myos=importlib.import_module("os")
myos.getcwd()
4.3 一個使用場景
一個定時任務的場景,資料庫存了許多這樣的定時任務:cmdb.tasks.get_host_info,它表示的是調用cmdb包下的tasks子產品下的get_host_info函數。我們怎麼實作,通過這種格式的字元串,去調用相應的函數呢?
大家可以思考思考怎麼做。
def exec_task(task_name):
if not task_name:
return -1, "task name must not None"
try:
module_name = task_name.rsplit(".", 1)[0]
method_name = task_name.rsplit(".", 1)[1]
# 動态導入tasks子產品
module_obj = __import__(module_name)
if not hasattr(module_obj, method_name):
return -1, "function not found"
except:
return -1, "has Error"
task = getattr(module_obj, method_name)
task()
5. 搜尋路徑
不知道大家有沒有思考過這樣一個問題,當我們導入一個子產品或者導入一個包的時候,python是去哪裡尋找這個子產品的呢?
Python搜尋子產品的路徑是由四部分構成的:
- 程式的主目錄、
- PATHONPATH目錄
- 标準連結庫目錄
- .pth檔案的目錄,
這四部分的路徑都存儲在sys.path 清單中。
pth檔案:pth檔案用于添加額外的sys.path即python檢索路徑,一般在github上下載下傳的程式包會有一個setup.py,執行該檔案會在(目前python環境下的site-packages檔案夾生成)一個.pth檔案
import sys
print(sys.path)
[
'/app/util-python/python-module', # 目前程式所在的目錄
'/usr/lib/python37.zip',
'/usr/lib/python3.7',
'/usr/lib/python3.7/lib-dynload',
'/app/python-virtualenv/aioms-env/lib/python3.7/site-packages' # 下載下傳的第三方包目錄
]
當我們的 導入一個包的時候,python解釋器依次在這些目錄搜尋。如果這些目錄中沒有找到,程式就會報錯
假設有一個檔案:
/app/test_pack.py
我們現在的程式路徑為:
/app/util-python/python-module/main.py
import test_pack
Traceback (most recent call last):
File "/app/util-python/python-module/main.py", line 5, in <module>
import test_pack
ModuleNotFoundError: No module named 'test_pack'
沒有任何意外,報錯了,找不到這個包
import sys
sys.path.append('/app')
import test_pack
test_pack.hello()
# 測試導入自定義路徑
print(sys.path)
[
'/app/util-python/python-module',
'/usr/lib/python37.zip',
'/usr/lib/python3.7',
'/usr/lib/python3.7/lib-dynload',
'/app/python-virtualenv/aioms-env/lib/python3.7/site-packages',
'/app' # 我們發現多了一個搜尋目錄,它在這個目錄下找到了我們的test_pack.py
]
是以:子產品與包的搜尋路徑不是固定不變的,我們可以自定義它,當然上面的方法隻是暫時的
是以,當我們程式出現這個錯誤的時候:
ModuleNotFoundError: No module named 'test_pack'
問題排查兩部曲:
- 檢視下載下傳安裝的包的路徑在什麼地方
- 使用sys.path看看,下載下傳完成的包在不在這裡面
6.相對導入與絕對導入
假設包結構:
main.py
packageA/
__init__.py
moduleA.py
moduleB.py
packageB/
__init__.py
moduleA.py
moduleB.py
對于
packageA/moduleA.py
:
絕對導入:所有的子產品import都從“根節點”開始。根節點的位置由sys.path中的路徑決定
from packageA import moduleB
相對導入:隻關心相對自己目前目錄的子產品位置
- . 目前目錄同級查找
- .. 目前目錄上級查找
from . import moduleB
7. 交叉引用(導入循環)
什麼叫做交叉導入,就是兩個包互相導入
main.py
package/
__init__.py
moduleA.py
moduleB.py
from . import moduleB
這裡不想拓展太多了,推薦大家都使用絕對導入就完事了!免得給自己挖坑
moduleA.py:
from moduleB import hello_b
moduleB.py:
from moduleA import hello_a
在這裡面:子產品A調用了子產品B的某方法、而子產品B也調用了子產品A的某方法,這個就叫交叉導入
首先我們看看這樣會導緻什麼問題
ImportError: cannot import name 'hello_b' from 'moduleB'
這樣将會抛出異常。異常的原因是:在導入的時候,moduleA需要通路moduleB的hello_b,但是沒有hello_b還沒有初始化完成。是以就會抛出異常
通常來說:這是由于大型的Python工程中,架構設計不當,出現的子產品間互相引用的情況
解決辦法:
- 架構優化,解除互相引用的關系
- import語句放置在子產品的最後
- import語句放置在函數中
8. 安裝第三方包
什麼是第三方包呢?在程式設計的圈子裡面流行着這麼一句話:“不要重複造輪子!”。這裡的輪子其實就是我們的第三方包。當我們想要制造一輛小汽車,我們直接使用現有的零件拼裝起來就行。就不需要再從0開始,造輪子、發動機等等。因為别人已經造好了。
在計算機行業裡面,别人造好了輪子,總要有地方存起來,可以讓其他使用者看到和使用。這就需要一個權威的三方機構去管理這些輪子。
PyPI就是這麼一個角色, PyPI(Python Package Index)是python官方的第三方庫的倉庫,所有人都可以下載下傳第三方庫或上傳自己開發的庫到PyPI。
那怎麼去管理這些三方包呢?市面上有很多方法。但是使用最廣泛的是
pip
pip 是一個現代的,通用的 Python 包管理工具,該工具提供了對Python 包的查找、下載下傳、安裝、解除安裝的功能
7.1 pip 安裝
# 檢視pip版本,可以判斷pip是否安裝
pip -v
一般情況下,我們從官網上下載下傳的python安裝以後,會自帶這個工具,我們無需過多操心,但是,如果很不巧,你的Python版本下恰好沒有pip這個工具,怎麼辦呢?
對于linux使用者:
$ curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py # 下載下傳安裝腳本
$ sudo python get-pip.py # 運作安裝腳本
sudo apt-get install python-pip
window使用者類似,隻需要去官網上下載下傳pip安裝包,再使用python安裝即可
7.2 安裝第三方包
pip install Django # 安裝最新版本
pip install Django==1.0.4 # 指定版本
pip install Django>=1.0.1 # 指定最小版本
pip install Django<=1.0.1 # 指定最大版本
由于 PyPI鏡像源是國外的,有時候下載下傳會非常緩慢,這個時候我們可以使用國内鏡像源
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple Django
- 清華:https://pypi.tuna.tsinghua.edu.cn/simple
- 阿裡雲:http://mirrors.aliyun.com/pypi/simple/
- 豆瓣:http://pypi.douban.com/simple/
- ......還有其他可自行搜尋
7.3 其他操作
pip uninstall Django # 解除安裝已安裝的庫
pip install --upgrade Djano==2.0.1 # 更新已安裝的包
pip list # 列出已安裝的庫
pip freeze > requirements.txt # 将目前的項目依賴導出的文本檔案中
pip install -r requirements.txt # 根據上面導出的文本檔案裡面的依賴進行安裝