今天買了一本關于子產品的書,說實話,子產品真的太多了,小編許多也不知道,要是把子產品全講完,可能得出本書了,是以小編在自己有限的能力範圍内在這裡淺析一下自己的見解,同時講講幾個常用的子產品。
這裡是2018.3.6對此文章更新,為了讓此文章更詳細,此前文章标題為python 淺析子產品的導入和調用,寫于此示己。
一,子產品
什麼是子產品?
在計算機程式的開發過程中,随着程式代碼越寫越多,在一個檔案裡面,代碼會越來越長,越來越不容易維護。
為了編寫可以維護的代碼,我們把很多函數分組,分别放到不同的檔案,這樣,每個檔案包含的代碼就相對比較少,很多程式設計語言都采用這種組織代碼的方式。在python中,一個.py檔案就稱之為一個子產品。
使用子產品有什麼好處?
1,最大的好處就是大大的提高了代碼的可維護性,其次,編寫代碼不必從零開始,當一個子產品編寫完畢,就可以被其他地方引用。我們在編寫程式的時候,也經常引用其他子產品,包括python内置的子產品和來自第三方的子產品。
2,使用子產品還可以避免函數名和變量名之間的沖突,每個子產品有獨立的命名空間,是以相同名字的函數和變量完全可以分别存在不同的子產品中,是以,我們自己編寫子產品時候,不必考慮名字會和其他子產品沖突。
子產品分類
子產品分為三種:
- 内置标準子產品(又稱為标準庫),執行help('modules')檢視所有python自帶子產品清單
- 第三方開源子產品,可以通過pip install modules 聯網安裝
- 自定義安裝
下面介紹一下如何導入子產品:
下面是常用的幾種導入子產品的方式:
子產品調用的方法
import module
from module import xx
from module.xx.xx import xx as rename
from module.xx.xx import *
注意:子產品一旦被調用,即相當于執行了另一個py檔案裡面的代碼
在Python 中用關鍵字import來導入某個子產品:
import modname
比如要導入子產品numpy,就可以在檔案最開始的地方用import numpy 來引入。在調用numpy子產品的函數時,必須這樣引入:
子產品名.函數名
import numpy
numpy.arange(5)
與第一種方法的差別是:funcname被直接導入到本地名字空間取了,是以他可以直接使用,而不需要加上子產品名的限定 * 表示,該子產品的所有公共對象(public objects)都被導入到目前的名稱空間,也就是任何隻要不是以 "_" 開始的東西都會被導入。
為什麼必須加上子產品名這樣調用呢?因為可能存在這樣一種情況,在多個子產品下含有相同名稱的函數,此時如果隻是通過函數名來調用,解釋器無法知道到底要調用那個函數,是以如果像上述這種情況引入子產品的時候,調用函數必須加上子產品名。
有時候我們隻需要引入子產品中的某個函數,隻需要引入該函數即可,此時通過下面語句實作:
form modname import funcname
(form modname import fa,fb,fc
form modname import * 但是這個得謹慎使用 )
當然可以通過通過這種方法不僅可以引入函數,還可以引入常量,通過這種方式引入的時候,調用函數時隻能給出函數名,不能給出子產品,但是當兩個子產品中含有相同名稱函數的時候,後面一次引入會覆寫前一次引入。是以有以下建議:
1) 如果你經常通路子產品的屬性和方法,且不想一遍又一遍的敲入子產品名稱,使用 form module import
2) 如果你想要有選擇性地導入某些屬性和方法,而不想要其他的,使用 form module import
3)如果子產品包含的屬性和方法與你的某個子產品同名,你必須使用 import module 來避免名字沖突
4)盡量少使用 form module import *,因為判定一個特殊的函數或屬性是從哪裡來的有些困難,并且會造成調試和重構都更困難。
還有一種導入子產品的方法就是内建函數 _import_ ()
除了前兩種使用 import 導入的方法以外,我們還可以使用内建函數來導入 module。兩者的差別是: import 後面必須跟的是一個類型(type),而 _import_()的參數是一個字元串,這個字元串可能來自配置檔案,也可能是某個表達式計算結果。例如:
mymodule = _import_ ( 'module_name ' )
一般情況應該使用import , 但有幾個例外
1)module文檔告訴你要用from-import的
2)導入一個包元件。需要一個包裡面的某個子子產品,一般用from A.b import c比import A.b.c 更友善 且不會冒混淆的危險.
你也許會想到,如果不同的人編寫的子產品名稱相同怎麼辦?為了避免子產品名沖突,Python又引入了按目錄來組織子產品的方法,稱為包(Package)
包的引入解決了子產品名沖突,就是隻要頂層的包名字不與别人沖突,那麼子產品都不會與别人沖突。
注意:每個包目錄下都會有一個 _init_.py 的檔案,這個檔案是必須存在的,否則,Python就把這個目錄當成普通目錄,而不是一個包。 _init_.py 可以是空檔案,也可以是有Python代碼,因為 _init_.py 本身就是一個子產品,對于我下面的項目來說,子產品名就是 mycompany

子產品查找路徑
初學者會發現,自己寫的子產品隻能在目前路徑下的程式裡面才能導入,換一個目錄再導入自己的子產品就報錯了,,這是為什麼呢?
答案就是:這與導入路徑有關
import sys
print(sys.path)
輸出結果入下(這是windows下的目錄):
['D:\\pycode\\子產品學習', 'D:\\pycode', 'D:\\python3\\python36.zip',
'D:\\python3\\DLLs', 'D:\\python3\\lib',
'D:\\python3',
'D:\\python3\\lib\\site-packages',
'D:\\python3\\lib\\site-packages\\win32',
'D:\\python3\\lib\\site-packages\\win32\\lib',
'D:\\python3\\lib\\site-packages\\Pythonwin']
是以python解釋器就會按照清單順序去依次到每隔目錄下去比對你要導入的子產品名,隻要在一個目錄下比對到了該子產品名,就立馬不繼續往下找,如果找完了依舊沒有找到,那麼就會報錯。
(注意,可能是linux下,清單的第一個元素為空,即代表目前目錄,是以你自己定義的子產品在目前目錄會被優先導入)
子產品的調用
首先舉個例子:
def main()
pass
mian()
如果直接在子產品裡面調用這個函數,那麼,假設其他子產品需要import目前子產品,那麼這個程式也會執行,但是其他子產品想要的隻是import而已,是以顯然,這樣是不可取的,我們需要将這種調用方式改成下面這種。
if __name__ == '__main__':
main()
這樣的話,隻有目前子產品被執行的時候,這個函數才被調用,程式才執行。
二,包(package)
注意:如果代碼出現 .pyc 檔案,這個不需要擔心,因為 pyc檔案是py檔案編譯後生成的位元組碼檔案,不需要自己建立,在你成功導入包後并運作成功後會自動生成,無需理會。
包的導入
當你的子產品檔案越來越多的時候,就需要對子產品檔案進行劃分,比如把負責跟資料庫互動的都放在一個檔案夾,把頁面互動相關的放在一個檔案夾。
└── my_proj
├── crm #代碼目錄
│ ├── admin.py
│ ├── apps.py
│ ├── models.py
│ ├── tests.py
│ └── views.py
├── manage.py
└── my_proj #配置檔案目錄
├── settings.py
├── urls.py
└── wsgi.py
比如上面這樣的,一個檔案夾管理多個子產品檔案,這個檔案夾就稱為包,那麼不同的包之間如何互相導入呢?
包就是檔案夾,但該檔案夾下必須存在 __init__.py 檔案, 該檔案的内容可以為空。__int__.py用于辨別目前檔案夾是一個包
from crm import views
views.sayhi()
其中crm/views.py内容如下:
def sayhi():
print('hello world!')
跨子產品導入包
目錄結構如下:
├── __init__.py
├── crm
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── models.py
│ ├── tests.py
│ ├── views.py
├── manage.py
└── proj
├── __init__.py
├── settings.py
├── urls.py
└── wsgi.py
根據上面的結構,如何實作在crm/views.py裡導入
proj/settings.py
子產品?
from proj import views
結果:
Traceback (most recent call last):
File "D:/crm/views.py", line 1, in <module>
from proj import views
ModuleNotFoundError: No module named 'proj'
為什麼直接導入會報錯呢?
是因為路徑找不到,proj/settings.py 相當于是crm/views.py的父親(crm)的兄弟(proj)的兒子(settings.py),settings.py算是views.py的表弟啦,在views.py裡隻能導入同級别兄弟子產品代碼,或者子級别包裡的子產品,根本不知道表弟表哥的存在。這可怎麼辦呢?
答案是添加環境變量,把父親級的路徑添加到sys.path中,就可以了,這樣導入 就相當于從父親級開始找子產品了。
os.path.dirname(path) 傳回path的目錄。其實就是os.path.split(path)的第一個元素
crm/views.py中添加環境變量
import sys
import os
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
#__file__的是列印目前被執行的子產品.py檔案相對路徑,注意是相對路徑
print(BASE_DIR)
sys.path.append(BASE_DIR)
from proj import settings
注意:此時在proj/settings.py寫上import urls會有問題麼?
答案是肯定會有問題的,因為現在的程式入口是views.py , 你在settings.py導入import urls,其實相當于在crm目錄找urls.py,而不是proj目錄,若想正常導入,要改成如下:
DATABASES= {
'host':'localhost'
}
from proj import urls #proj這一層目錄已經添加到sys.path裡,可以直接找到
print('in proj/settings.py')
這裡就涉及了絕對導入和相對導入了,下面直接說一下
絕對導入和相對導入
在linux裡可以通過cd ..回到上一層目錄 ,cd ../.. 往上回2層,這個..就是指相對路徑,在python裡,導入也可以通過
..
例如:
├── __init__.py
├── crm
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── models.py
│ ├── tests.py
│ ├── views.py #from ..proj import settings
├── manage.py
└── proj
├── __init__.py
├── settings.py #from .import urls
├── urls.py
└── wsgi.py
views.py裡代碼
from ..proj import settings
def sayhi():
print('hello world!')
print(settings.DATABASES)
執行結果報錯了(linux)
Traceback (most recent call last):
File "my_proj/crm/views.py", line 4, in <module>
from ..proj import settings
SystemError: Parent module '' not loaded, cannot perform relative import
或者報錯如下(windows):
Traceback (most recent call last):
D:/子產品學習
File "D:/crm/views.py", line 9, in <module>
from .. import settings
ValueError: attempted relative import beyond top-level package
其實這兩個錯誤的原因歸根結底是一樣的:在涉及到相對導入時,package所對應的檔案夾必須正确的被python解釋器視作package,而不是普通檔案夾。否則由于不被視作package,無法利用package之間的嵌套關系實作python中包的相對導入。
檔案夾被python解釋器視作package需要滿足兩個條件:
- 檔案夾中必須有__init__.py檔案,該檔案可以為空,但必須存在該檔案。
- 不能作為頂層子產品來執行該檔案夾中的py檔案(即不能作為主函數的入口)。
是以這個問題的解決辦法就是,既然你在views.py裡執行了相對導入,那就不要把views.py當作入口程式,可以通過上一級的manage.py調用views.py
.
├── __init__.py
├── crm
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── models.py
│ ├── tests.py
│ ├── views.py #from ..proj import settings
├── manage.py #from crm import views
└── proj
├── __init__.py
├── settings.py #from .import urls
├── urls.py
└── wsgi.py
事實證明還是不行,報錯
ValueError: attempted relative import beyond top-level package
但把
from ..proj import settings
改成
from . import models
後卻執行成功了,為什麼呢?
from .. import models
會報錯的原因是,這句代碼會把manage.py所在的這一層視作package,但實際上它不是,因為package不能是頂層入口代碼,若想不出錯,隻能把manage.py往上再移一層。
正确的代碼目錄結構如下
packages/
├── __init__.py
├── manage.py #from my_proj.crm import views
└── my_proj
├── crm
│ ├── admin.py
│ ├── apps.py
│ ├── models.py
│ ├── tests.py
│ ├── views.py #from . import models; from ..proj import settings
└── proj
├── __init__.py
├── settings.py
├── urls.py
└── wsgi.py
再執行manage.py就不會報錯了。
注意:雖然python支援相對導入,但對子產品間的路徑關系要求比較嚴格,處理不當就容易出錯,是以并不建議在項目裡經常使用。
不經一番徹骨寒 怎得梅花撲鼻香