1. 程式人生 > 實用技巧 >php 字串分割函式 str_split,chunk_split

php 字串分割函式 str_split,chunk_split

一、模組介紹


1,什麼是模組?

# 之前在編寫ATM作業時,思路是先將程式中都需要有哪些功能定義出來,然後在需要用的地方呼叫即可。
# 比起之前通篇壘程式碼的方式,將重複要用的功能定義成函式會讓程式更加簡潔,這不能不算做是一種進步,
# 但問題是,隨著程式功能越來越多,再將所有的程式碼都放到一起,程式的組織結構仍然會不清晰,不方便管理,
# 以後我們寫程式,都是分檔案的,如果多個檔案中都需要用到同一段功能,難道我們要重複編寫該功能嗎?很明顯不能。
# 這就需要我們找到一種解決方案,能夠將程式中經常要用的功能集合到一起,然後在想用的地方隨時匯入使用,
# 這幾乎就是模組的全部含義了

# 最後總結:
# 模組就是一組功能的集合體,我們的程式可以匯入模組來複用模組裡的功能。
# 常見的場景:一個模組就是一個包含了一組功能的python檔案,比如spam.py,模組名為spam,可以通過import spam使用。

# 在python中,模組的使用方式都是一樣的,但其實細說的話,模組可以分為四個通用類別: 

  1,使用python編寫的.py檔案

  2,已被編譯為共享庫或DLL的C或C++擴充套件

  3,把一系列模組組織到一起的資料夾(注:資料夾下有一個__init__.py檔案,該資料夾稱之為包)

  4,使用C編寫並連結到python直譯器的內建模組

2,為什麼要使用模組?

# 1,從檔案級別組織程式,更方便管理
隨著程式的發展,功能越來越多,為了方便管理,我們通常將程式分成一個個的檔案,這樣做程式的結構更清晰,方便管理。
這時我們不僅僅可以把這些檔案當做指令碼去執行,還可以把他們當做模組來匯入到其他的模組中,實現了功能的重複利用。

# 2,拿來主義,提升開發效率
同樣的原理,我們也可以下載別人寫好的模組然後匯入到自己的專案中使用,這種拿來主義,可以極大地提升我們的開發效率

# tip:
如果你退出python直譯器然後重新進入,那麼你之前定義的函式或者變數都將丟失,因此我們通常將程式寫到檔案中以便永久儲存下來,
需要時就通過python test.py方式去執行,此時test.py被稱為指令碼script。

3,以 spam.py 為例來介紹模組的使用:檔名 spam.py,模組名 spam

# spam.py
print('from the spam.py')

money = 1000

def read1():
    print('spam模組:',money)

def read2():
    print('spam模組')
    read1()

def change():
    global money
    money = 0

二、使用模組之 import


1,import 的使用

# 模組可以包含可執行的語句和函式的定義,這些語句的目的是初始化模組,它們只在模組名第一次遇到匯入import語句時才執行
# (import語句是可以在程式中的任意位置使用的,且針對同一個模組很import多次,為了防止你重複匯入,python的優化手段是:
# 第一次匯入後就將模組名載入到記憶體了,後續的import語句僅是對已經載入到記憶體中的模組物件增加了一次引用,不會重新執行模組內的語句),如下

# test.py
# 只在第一次匯入時才執行spam.py內程式碼,此處的顯式效果是隻列印一次'from the spam.py',當然其他的頂級程式碼也都被執行了,只不過沒有顯示效果.
import spam
import spam
import spam
import spam

'''
執行結果:
from the spam.py
'''

tip:我們可以從 sys.module 中找到當前已經載入的模組,sys.module 是一個字典,內部包含模組名與模組物件的對映,該字典決定了匯入模組時是否需要重新匯入。

2,在第一次匯入模組時會做三件事,重複匯入會直接引用記憶體中已經載入好的結果

# 1,為原始檔(spam模組)建立新的名稱空間,在spam中定義的函式和方法若是使用到了global時訪問的就是這個名稱空間。

# 2,在新建立的名稱空間中執行模組中包含的程式碼,見初始匯入import spam
    提示:匯入模組時到底執行了什麼?
    In fact function definitions are also ‘statements’ that are 
    ‘executed’; the execution of a module-level function definition 
    enters the function name in the module’s global symbol table.
    事實上函式定義也是“被執行”的語句,模組級別函式定義的執行將函式名放
    入模組全域性名稱空間表,用globals()可以檢視

# 3,建立名字spam來引用該名稱空間
    這個名字和變數名沒什麼區別,都是‘第一類的’,且使用spam.名字的方式
    可以訪問spam.py檔案中定義的名字,spam.名字與test.py中的名字來自
    兩個完全不同的地方。

3,被匯入的模組有獨立的名稱空間

每個模組都是一個獨立的名稱空間,定義在這個模組中的函式,把這個模組的名稱空間當做全域性名稱空間,這樣我們在編寫自己的模組時,就不用擔心我們定義在自己模組中全域性變數會在被匯入時,與使用者的全域性變數衝突。

# test.py
import spam 
money = 10
print(spam.money)

'''
執行結果:
from the spam.py
1000
'''

測試一:money與spam.money不衝突
測試一:money與spam.money不衝突
# test.py
import spam
def read1():
    print('========')
spam.read1()

'''
執行結果:
from the spam.py
spam->read1->money 1000
'''

測試二:read1與spam.read1不衝突
測試二:read1與spam.read1不衝突
# test.py
import spam
money = 1
spam.change()
print(money)

'''
執行結果:
from the spam.py
1
'''

測試三:執行spam.change()操作的全域性變數money仍然是spam中的
測試三:執行spam.change()操作的全域性變數money仍然是spam中的

4,為模組名起別名

為已經匯入的模組起別名的方式對編寫可擴充套件的程式碼很有用

import spam as sm

print(sm.money)

有兩中 sql模組 mysql和 oracle,根據使用者的輸入,選擇不同的 sql功能

# mysql.py
def sqlparse():
    print('from mysql sqlparse')
# oracle.py
def sqlparse():
    print('from oracle sqlparse')

# test.py
db_type=input('>>: ')
if db_type == 'mysql':
    import mysql as db
elif db_type == 'oracle':
    import oracle as db

db.sqlparse()
View Code

假設有兩個模組 xmlreader.py和 csvreader.py,它們都定義了函式 read_data(filename):用來從檔案中讀取一些資料,但採用不同的輸入格式。可以編寫程式碼來選擇性地挑選讀取模組。

if file_format == 'xml':
    import xmlreader as reader
elif file_format == 'csv':
    import csvreader as reader
data = reader.read_date(filename)

5,在一行匯入多個模組

import sys,os,re

三、使用模組from...import...


1,from...import...的使用

from spam import read1,read2

2,from...import 與import的對比

# 唯一的區別就是:使用from...import...則是將spam中的名字直接匯入到當前的名稱空間中,
# 所以在當前名稱空間中,直接使用名字就可以了、無需加字首:spam.

# from...import...的方式有好處也有壞處
    好處:使用起來方便了
    壞處:容易與當前執行檔案中的名字衝突

驗證一:當前位置直接使用read1和read2就好了,執行時,仍然以spam.py檔案全域性名稱空間

# 測試一:匯入的函式read1,執行時仍然回到spam.py中尋找全域性變數money
# test.py
from spam import read1
money = 1000
read1()

'''
執行結果:
from the spam.py
spam->read1->money 1000
'''

# 測試二:匯入的函式read2,執行時需要呼叫read1(),仍然回到spam.py中找read1()
# test.py
from spam import read2
def read1():
    print('==========')
read2()


'''
執行結果:
from the spam.py
spam->read2 calling read
spam->read1->money 1000
'''
View Code

驗證二:如果當前有重名read1或者read2,那麼會有覆蓋效果。

# 測試三:匯入的函式read1,被當前位置定義的read1覆蓋掉了
# test.py
from spam import read1
def read1():
    print('==========')
read1()


'''
執行結果:
from the spam.py
==========
'''
View Code

驗證三:匯入的方法在執行時,始終是以原始檔為準的

from spam import money,read1
money = 100     # 將當前位置的名字money繫結到了100
print(money)     # 列印當前的名字
read1()             # 讀取spam.py中的名字money,仍然為1000


'''
from the spam.py
100
spam->read1->money 1000
'''     
View Code

3,也支援 as

from spam import read1 as read

4,一行匯入多個名字

from spam import read1,read2,money

5,from...import *

# from spam import * 把spam中所有的不是以下劃線(_)開頭的名字都匯入到當前位置

#大部分情況下我們的python程式不應該使用這種匯入方式,因為*你不知道你匯入什麼名字,很有可能會覆蓋掉你之前已經定義的名字。
# 而且可讀性極其的差,在互動式環境中匯入時沒有問題。
from spam import *     # 將模組spam中所有的名字都匯入到當前名稱空間

print(money)
print(read1)
print(read2)
print(change)


'''
執行結果:
from the spam.py
1000
<function read1 at 0x1012e8158>
<function read2 at 0x1012e81e0>
<function change at 0x1012e8268>
'''
View Code

可以使用__all__來控制*(用來發布新版本),在spam.py中新增一行

__all__=['money','read1']     # 這樣在另外一個檔案中用from spam import *就這能匯入列表中規定的兩個名字

6,模組迴圈匯入問題

模組迴圈/巢狀匯入丟擲異常的根本原因是由於在 python中模組被匯入一次之後,就不會重新匯入,只會在第一次匯入時執行模組內程式碼。

在我們的專案中應該儘量避免出現迴圈/巢狀匯入,如果出現多個模組都需要共享的資料,可以將共享的資料集中存放到某一個地方。

在程式出現了迴圈/巢狀匯入後的異常分析、解決方法如下:

# 示範檔案內容如下

# m1.py
print('正在匯入m1')
from m2 import y

x = 'm1'



# m2.py
print('正在匯入m2')
from m1 import x

y = 'm2'



# run.py
import m1

# 測試一
執行run.py會丟擲異常
正在匯入m1
正在匯入m2
Traceback (most recent call last):
  File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa練習目錄/aa.py", line 1, in <module>
    import m1
  File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa練習目錄/m1.py", line 2, in <module>
    from m2 import y
  File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa練習目錄/m2.py", line 2, in <module>
    from m1 import x
ImportError: cannot import name 'x'

# 測試一結果分析
先執行run.py--->執行import m1,開始匯入m1並執行其內部程式碼--->列印內容"正在匯入m1"
--->執行from m2 import y 開始匯入m2並執行其內部程式碼--->列印內容“正在匯入m2”--->執行from m1 import x,由於m1已經被匯入過了,所以不會重新匯入,所以直接去m1中拿x,然而x此時並沒有存在於m1中,所以報錯


# 測試二:執行檔案不等於匯入檔案,比如執行m1.py不等於匯入了m1
直接執行m1.py丟擲異常
正在匯入m1
正在匯入m2
正在匯入m1
Traceback (most recent call last):
  File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa練習目錄/m1.py", line 2, in <module>
    from m2 import y
  File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa練習目錄/m2.py", line 2, in <module>
    from m1 import x
  File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa練習目錄/m1.py", line 2, in <module>
    from m2 import y
ImportError: cannot import name 'y'


# 測試二分析
執行m1.py,列印“正在匯入m1”,執行from m2 import y ,匯入m2進而執行m2.py內部程式碼--->列印"正在匯入m2",執行from m1 import x,此時m1是第一次被匯入,執行m1.py並不等於匯入了m1,於是開始匯入m1並執行其內部程式碼--->列印"正在匯入m1",執行from m1 import y,由於m1已經被匯入過了,所以無需繼續匯入而直接問m2要y,然而y此時並沒有存在於m2中所以報錯



# 解決方法:
方法一:匯入語句放到最後

# m1.py
print('正在匯入m1')

x = 'm1'

from m2 import y

# m2.py
print('正在匯入m2')
y = 'm2'

from m1 import x

方法二:匯入語句放到函式中
# m1.py
print('正在匯入m1')

def f1():
    from m2 import y
    print(x,y)

x = 'm1'

# f1()

# m2.py
print('正在匯入m2')

def f2():
    from m1 import x
    print(x,y)

y = 'm2'

# run.py
import m1

m1.f1()
示例檔案
# m1.py
f1()
print('正在匯入m1')
import m2

x = 'm1'

print(m2.y)


# m2.py
print('正在匯入m2')
import m1

y = 'm2'

# run.py
import m1
思考

四、模組的過載(瞭解)


考慮到效能的原因,每個模組只被匯入一次,放入字典 sys.module中,如果你改變了模組的內容,你必須重啟程式,python不支援重新載入或解除安裝之前匯入的模組,

有的人可能會想到直接從 sys.module中刪除一個模組不就可以解除安裝了嗎,注意了,你刪了 sys.module中的模組物件仍然可能被其他程式的元件所引用,因而不會被清楚。

特別的對於我們引用了這個模組中的一個類,用這個類產生了很多物件,因而這些物件都有關於這個模組的引用。

如果只是你想互動測試的一個模組,使用 importlib.reload(), e.g. import importlib; importlib.reload(modulename),這隻能用於測試環境。

def func1():
    print('func1')
zz.py的初始內容
import time,importlib
import zz

time.sleep(20)
# importlib.reload(zz)
zz.func1()
執行test.py

在20秒的等待時間裡,修改 zz.py中func1的內容,等待 test.py的結果。

開啟importlib註釋,重新測試

五、py檔案區分兩種用途:模組與指令碼


如下:

# 編寫好的一個python檔案可以有兩種用途:
    一:指令碼,一個檔案就是整個程式,用來被執行
    二:模組,檔案中存放著一堆功能,用來被匯入使用


# python為我們內建了全域性變數__name__,
    當檔案被當做指令碼執行時:__name__ 等於'__main__'
    當檔案被當做模組匯入時:__name__等於模組名

# 作用:用來控制.py檔案在不同的應用場景下執行不同的邏輯
    if __name__ == '__main__':
# fib.py

def fib(n):        # write Fibonacci series up to n
    a, b = 0, 1
    while b < n:
        print(b, end=' ')
        a, b = b, a+b
    print()

def fib2(n):       # return Fibonacci series up to n
    result = []
    a, b = 0, 1
    while b < n:
        result.append(b)
        a, b = b, a+b
    return result

if __name__ == "__main__":
    import sys
    fib(int(sys.argv[1]))


# 執行:python fib.py <arguments>
python fib.py 50     # 在命令列
View Code

六、模組搜尋路徑


模組的查詢順序是:記憶體中已經載入的模組->內建模組->sys.path路徑中包含的模組

# 模組的查詢順序
1、在第一次匯入某個模組時(比如spam),會先檢查該模組是否已經被載入到記憶體中(當前執行檔案的名稱空間對應的記憶體),如果有則直接引用
    tip:python直譯器在啟動時會自動載入一些模組到記憶體中,可以使用sys.modules檢視
2、如果沒有,直譯器則會查詢同名的內建模組
3、如果還沒有找到就從sys.path給出的目錄列表中依次尋找spam.py檔案。


# sys.path的初始化的值來自於:
The directory containing the input script (or the current directory when no file is specified).
PYTHONPATH (a list of directory names, with the same syntax as the shell variable PATH).
The installation-dependent default.

# 需要特別注意的是:我們自定義的模組名不應該與系統內建模組重名。雖然每次都說,但是仍然會有人不停的犯錯。 

# 在初始化後,python程式可以修改sys.path,路徑放到前面的優先於標準庫被載入。

1 >>> import sys
2 >>> sys.path.append('/a/b/c/d')
3 >>> sys.path.insert(0,'/x/y/z') #排在前的目錄,優先被搜尋
注意:搜尋時按照sys.path中從左到右的順序查詢,位於前的優先被查詢,sys.path中還可能包含.zip歸檔檔案和.egg檔案,python會把.zip歸檔檔案當成一個目錄去處理,

# 首先製作歸檔檔案:zip module.zip foo.py bar.py 
import sys
sys.path.append('module.zip')
import foo,bar

# 也可以使用zip中目錄結構的具體位置
sys.path.append('module.zip/lib/python')


# windows下的路徑不加r開頭,會語法錯誤
sys.path.insert(0,r'C:\Users\Administrator\PycharmProjects\a')
 

# 至於.egg檔案是由setuptools建立的包,這是按照第三方python庫和擴充套件時使用的一種常見格式,.egg檔案實際上只是添加了額外元資料(如版本號,依賴項等)的.zip檔案。

# 需要強調的一點是:只能從.zip檔案中匯入.py,.pyc等檔案。使用C編寫的共享庫和擴充套件塊無法直接從.zip檔案中載入(此時setuptools等打包系統有時能提供一種規避方法),且從.zip中載入檔案不會建立.pyc或者.pyo檔案,因此一定要事先建立他們,來避免載入模組是效能下降。
詳細的

官網解釋:

# 官網連結:https://docs.python.org/3/tutorial/modules.html#the-module-search-path

搜尋路徑:
當一個命名為spam的模組被匯入時
    直譯器首先會從內建模組中尋找該名字
    找不到,則去sys.path中找該名字

sys.path從以下位置初始化
    1 執行檔案所在的當前目錄
    2 PTYHONPATH(包含一系列目錄名,與shell變數PATH語法一樣)
    3 依賴安裝時預設指定的

注意:在支援軟連線的檔案系統中,執行指令碼所在的目錄是在軟連線之後被計算的,換句話說,包含軟連線的目錄不會被新增到模組的搜尋路徑中

在初始化後,我們也可以在python程式中修改sys.path,執行檔案所在的路徑預設是sys.path的第一個目錄,在所有標準庫路徑的前面。這意味著,當前目錄是優先於標準庫目錄的,需要強調的是:我們自定義的模組名不要跟python標準庫的模組名重複,除非你是故意的。。。

七、編譯 python檔案(瞭解)


為了提高載入模組的速度,強調強調強調:提高的是載入速度而絕非執行速度。python直譯器會在__pycache__目錄中下快取每個模組編譯後的版本,格式為:module.version.pyc。通常會包含python的版本號。例如,在CPython3.3版本下,spam.py模組會被快取成__pycache__/spam.cpython-33.pyc。這種命名規範保證了編譯後的結果多版本共存。

Python檢查原始檔的修改時間與編譯的版本進行對比,如果過期就需要重新編譯。這是完全自動的過程。並且編譯的模組是平臺獨立的,所以相同的庫可以在不同的架構的系統之間共享,即pyc使一種跨平臺的位元組碼,類似於JAVA和.NET,是由 python虛擬機器來執行的,但是 pyc的內容跟 python的版本相關,不同的版本編譯後的 pyc檔案不同,2.5編譯的 pyc檔案不能到3.5上執行,並且 pyc檔案是可以反編譯的,因而它的出現僅僅是用來提升模組的載入速度的,不是用來加密的。

# python直譯器在以下兩種情況下不檢測快取
# 1 如果是在命令列中被直接匯入模組,則按照這種方式,每次匯入都會重新編譯,並且不會儲存編譯後的結果(python3.3以前的版本應該是這樣)
    python -m spam.py

# 2 如果原始檔不存在,那麼快取的結果也不會被使用,如果想在沒有原始檔的情況下來使用編譯後的結果,則編譯後的結果必須在源目錄下
sh-3.2# ls
__pycache__ spam.py
sh-3.2# rm -rf spam.py 
sh-3.2# mv __pycache__/spam.cpython-36.pyc ./spam.pyc
sh-3.2# python3 spam.pyc 
spam
 

# 提示:
1.模組名區分大小寫,foo.py與FOO.py代表的是兩個模組
2.你可以使用-O或者-OO轉換python命令來減少編譯模組的大小
    -O轉換會幫你去掉assert語句
    -OO轉換會幫你去掉assert語句和__doc__文件字串
    由於一些程式可能依賴於assert語句或文件字串,你應該在在確認需要
    的情況下使用這些選項。
3.在速度上從.pyc檔案中讀指令來執行不會比從.py檔案中讀指令執行更快,只有在模組被載入時,.pyc檔案才是更快的

4.只有使用import語句是才將檔案自動編譯為.pyc檔案,在命令列或標準輸入中指定執行指令碼則不會生成這類檔案,因而我們可以使用compieall模組為一個目錄中的所有模組建立.pyc檔案

模組可以作為一個指令碼(使用python -m compileall)編譯Python源  
python -m compileall /module_directory 遞迴著編譯
如果使用python -O -m compileall /module_directory -l則只一層
  
命令列裡使用compile()函式時,自動使用python -O -m compileall
  
詳見:https://docs.python.org/3/library/compileall.html#module-compileall
詳細的

八、包介紹


1,什麼是包?

# 官網解釋
Packages are a way of structuring Python’s module namespace by using “dotted module names”
包是一種通過使用‘.模組名’來組織python模組名稱空間的方式。

# 具體的:包就是一個包含有__init__.py檔案的資料夾,所以其實我們建立包的目的就是為了用資料夾將檔案/模組組織起來

# 需要強調的是:
  1. 在python3中,即使包下沒有__init__.py檔案,import 包仍然不會報錯,而在python2中,包下一定要有該檔案,否則import 包報錯

  2. 建立包的目的不是為了執行,而是被匯入使用,記住,包只是模組的一種形式而已,包的本質就是一種模組

2,為什麼要使用包?

# 包的本質就是一個資料夾,那麼資料夾唯一的功能就是將檔案組織起來

# 隨著功能越寫越多,我們無法將所以功能都放到一個檔案中,於是我們使用模組去組織功能,而隨著模組越來越多,
# 我們就需要用資料夾將模組檔案組織起來,以此來提高程式的結構性和可維護性。

3,注意事項

# 1.關於包相關的匯入語句也分為import和from ... import ...兩種,但是無論哪種,無論在什麼位置,在匯入時都必須遵循一個原則:
# 凡是在匯入時帶點的,點的左邊都必須是一個包,否則非法。可以帶有一連串的點,如item.subitem.subsubitem,但都必須遵循這個原則。
# 但對於匯入後,在使用時就沒有這種限制了,點的左邊可以是包,模組,函式,類(它們都可以用點的方式呼叫自己的屬性)。

# 2、import匯入檔案時,產生名稱空間中的名字來源於檔案,import 包,產生的名稱空間的名字同樣來源於檔案,即包下的__init__.py,匯入包本質就是在匯入該檔案

# 3、包A和包B下有同名模組也不會衝突,如A.a與B.a來自倆個名稱空間

4,上課流程

1 實驗一
    準備:
        執行檔案為test.py,內容
        #test.py
        import aaa
        同級目錄下建立目錄aaa,然後自建空__init__.py(或者乾脆建包)

    需求:驗證匯入包就是在匯入包下的__init__.py

    解決:
        先執行看結果
        再在__init__.py新增列印資訊後,重新執行

2、實驗二
    準備:基於上面的結果

    需求:
        aaa.x
        aaa.y
    解決:在__init__.py中定義名字x和y

3、實驗三
    準備:在aaa下建立m1.py和m2.py
        #m1.py
        def f1():
            print('from 1')
        #m2.py
        def f2():
            print('from 2')
    需求:
        aaa.m1 #進而aaa.m1.func1()
        aaa.m2 #進而aaa.m2.func2()

    解決:在__init__.py中定義名字m1和m2,先定義一個普通變數,再引出如何匯入模組名,強調:環境變數是以執行檔案為準
    

4、實驗四
    準備:在aaa下新建包bbb

    需求:
        aaa.bbb

    解決:在aaa的__init__.py內匯入名字bbb

5、實驗五
    準備:
        在bbb下建立模組m3.py
        #m3.py
        def f3():
            print('from 3')
    需求:
        aaa.bbb.m3 #進而aaa.bbb.m3.f3()

    解決:是bbb下的名字m3,因而要在bbb的__init__.py檔案中匯入名字m3,from aaa.bbb import m3

6、實驗六
    準備:基於上面的結果

    需求:
        aaa.m1()
        aaa.m2()
        aaa.m3()
        進而實現
        aaa.f1()
        aaa.f2()
        aaa.f3()
        先用絕對匯入,再用相對匯入
        
    解決:在aaa的__init__.py中拿到名字m1、m2、m3
    包內模組直接的相對匯入,強調包的本質:包內的模組是用來被匯入的,而不是被執行的
    使用者無法區分模組是檔案還是一個包,我們定義包是為了方便開發者維護

7、實驗七
    將包整理當做一個模組,移動到別的目錄下,操作sys.path
流程

九、包的使用


1,示範檔案

glance/                       # Top-level package

├── __init__.py          # Initialize the glance package

├── api                      # Subpackage for api

│   ├── __init__.py

│   ├── policy.py

│   └── versions.py

├── cmd                    # Subpackage for cmd

│   ├── __init__.py

│   └── manage.py

└── db                      # Subpackage for db

    ├── __init__.py

    └── models.py
# 檔案內容

# policy.py
def get():
    print('from policy.py')

# versions.py
def create_resource(conf):
    print('from version.py: ',conf)

# manage.py
def main():
    print('from manage.py')

# models.py
def register_models(engine):
    print('from models.py: ',engine)

包所包含的檔案內容
檔案內容

執行檔案與示範檔案在同級目錄下

2,包的使用之 import

import glance.db.models

glance.db.models.register_models('mysql') 

單獨匯入包名稱時不會匯入包中所有包含的所有子模組,如:

# 在與glance同級的test.py中

import glance

glance.cmd.manage.main()


'''
執行結果:
AttributeError: module 'glance' has no attribute 'cmd'

'''

解決方法:

# glance/__init__.py
from . import cmd
 
# glance/cmd/__init__.py
from . import manage

執行:

# 在於glance同級的test.py中
import glance

glance.cmd.manage.main()

3,包的使用之 from...import...

需要注意的是 from後 import匯入的模組,必須是明確的一個不能帶點,否則會有語法錯誤,如:from a import b.c是錯誤語法。

from glance.db import models
models.register_models('mysql')
 
from glance.db.models import register_models
register_models('mysql')

4,from glance.api import *

在說模組時,我們已經討論過了從一個模組內匯入所有*,此處我們研究從一個包匯入所有*。

此處是想從包 api中匯入所有,實際上該語句只會匯入包 api下__init__.py檔案中定義的名字,我們可以在這個檔案中定義__all___:

# 在__init__.py中定義

x=10

def func():
    print('from api.__init.py')

__all__ = ['x','func','policy']

此時我們在於 glance同級的檔案中執行 from glance.api import *就匯入__all__中的內容(versions仍然不能匯入)。

練習:

# 執行檔案中的使用效果如下,請處理好包的匯入
from glance import *

get()
create_resource('a.conf')
main()
register_models('mysql')
# 在glance.__init__.py中
from .api.policy import get
from .api.versions import create_resource

from .cmd.manage import main
from .db.models import  register_models

__all__ = ['get','create_resource','main','register_models']
View Code

5,絕對匯入和相對匯入

我們的最頂級包 glance是寫給別人用的,然後在 glance包內部也會有彼此之間互相匯入的需求,這時候就有絕對匯入和相對匯入兩種方式:

絕對匯入:以 glance作為起始

相對匯入:用.或者..的方式最為起始(只能在一個包中使用,不能用於不同目錄內)

例如:我們在 glance/api/version.py中想要匯入 glance/cmd/manage.py

在glance/api/version.py

# 絕對匯入
from glance.cmd import manage
manage.main()

# 相對匯入
from ..cmd import manage
manage.main()

測試結果:注意一定要在於 glance同級的檔案中測試

from glance.api import versions 

6,包以及包所包含的模組都是用來被匯入的,而不是被直接執行的。而環境變數都是以執行檔案為準的

比如我們想在 glance/api/versions.py中匯入 glance/api/policy.py,有的人一看這倆模組是在同一個目錄下,十分開心的就去做了,它直接這麼做。

# 在version.py中
 
import policy
policy.get()

沒錯,我們單獨執行 version.py是一點問題沒有的,執行 version.py的路徑搜尋就是從當前路徑開始的,於是在匯入 policy時能在當前目錄下找到

但是你想啊,你子包中的模組 version.py極有可能是被一個 glance包同一級別的其他檔案匯入,比如我們在於 glance同級下的一個 test.py檔案中匯入 version.py,如下:

from glance.api import versions

'''
執行結果:
ImportError: No module named 'policy'
'''

'''
分析:
此時我們匯入versions在versions.py中執行
import policy需要找從sys.path也就是從當前目錄找policy.py,
這必然是找不到的
'''

7,絕對匯入與相對匯入總結

絕對匯入與相對匯入

# 絕對匯入: 以執行檔案的sys.path為起始點開始匯入,稱之為絕對匯入
#        優點: 執行檔案與被匯入的模組中都可以使用
#        缺點: 所有匯入都是以sys.path為起始點,匯入麻煩

# 相對匯入: 參照當前所在檔案的資料夾為起始開始查詢,稱之為相對匯入
#        符號: .代表當前所在檔案的檔案加,..代表上一級資料夾,...代表上一級的上一級資料夾
#        優點: 匯入更加簡單
#        缺點: 只能在匯入包中的模組時才能使用
      # 注意:
        1. 相對匯入只能用於包內部模組之間的相互匯入,匯入者與被匯入者都必須存在於一個包內
        2. attempted relative import beyond top-level package 

# 試圖在頂級包之外使用相對匯入是錯誤的,言外之意,必須在頂級包內使用相對匯入,每增加一個.代表跳到上一級資料夾,而上一級不應該超出頂級包

8,包的分發(瞭解)

https://packaging.python.org/distributing/

十、軟體開發規範


如下:

#===============>star.py
import sys,os
BASE_DIR=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(BASE_DIR)

from core import src

if __name__ == '__main__':
    src.run()
#===============>settings.py
import os

BASE_DIR=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
DB_PATH=os.path.join(BASE_DIR,'db','db.json')
LOG_PATH=os.path.join(BASE_DIR,'log','access.log')
LOGIN_TIMEOUT=5

"""
logging配置
"""
# 定義三種日誌輸出格式
standard_format = '[%(asctime)s][%(threadName)s:%(thread)d][task_id:%(name)s][%(filename)s:%(lineno)d]' \
                  '[%(levelname)s][%(message)s]' #其中name為getlogger指定的名字
simple_format = '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s'
id_simple_format = '[%(levelname)s][%(asctime)s] %(message)s'

# log配置字典
LOGGING_DIC = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'standard': {
            'format': standard_format
        },
        'simple': {
            'format': simple_format
        },
    },
    'filters': {},
    'handlers': {
        #列印到終端的日誌
        'console': {
            'level': 'DEBUG',
            'class': 'logging.StreamHandler',  # 列印到螢幕
            'formatter': 'simple'
        },
        #列印到檔案的日誌,收集info及以上的日誌
        'default': {
            'level': 'DEBUG',
            'class': 'logging.handlers.RotatingFileHandler',  # 儲存到檔案
            'formatter': 'standard',
            'filename': LOG_PATH,  # 日誌檔案
            'maxBytes': 1024*1024*5,  # 日誌大小 5M
            'backupCount': 5,
            'encoding': 'utf-8',  # 日誌檔案的編碼,再也不用擔心中文log亂碼了
        },
    },
    'loggers': {
        #logging.getLogger(__name__)拿到的logger配置
        '': {
            'handlers': ['default', 'console'],  # 這裡把上面定義的兩個handler都加上,即log資料既寫入檔案又列印到螢幕
            'level': 'DEBUG',
            'propagate': True,  # 向上(更高level的logger)傳遞
        },
    },
}


#===============>src.py
from conf import settings
from lib import common
import time

logger=common.get_logger(__name__)

current_user={'user':None,'login_time':None,'timeout':int(settings.LOGIN_TIMEOUT)}
def auth(func):
    def wrapper(*args,**kwargs):
        if current_user['user']:
            interval=time.time()-current_user['login_time']
            if interval < current_user['timeout']:
                return func(*args,**kwargs)
        name = input('name>>: ')
        password = input('password>>: ')
        db=common.conn_db()
        if db.get(name):
            if password == db.get(name).get('password'):
                logger.info('登入成功')
                current_user['user']=name
                current_user['login_time']=time.time()
                return func(*args,**kwargs)
        else:
            logger.error('使用者名稱不存在')

    return wrapper

@auth
def buy():
    print('buy...')

@auth
def run():

    print('''
    1 購物
    2 檢視餘額
    3 轉賬
    ''')
    while True:
        choice = input('>>: ').strip()
        if not choice:continue
        if choice == '1':
            buy()



#===============>db.json
{"egon": {"password": "123", "money": 3000}, "alex": {"password": "alex3714", "money": 30000}, "wsb": {"password": "3714", "money": 20000}}

#===============>common.py
from conf import settings
import logging
import logging.config
import json

def get_logger(name):
    logging.config.dictConfig(settings.LOGGING_DIC)  # 匯入上面定義的logging配置
    logger = logging.getLogger(name)  # 生成一個log例項
    return logger


def conn_db():
    db_path=settings.DB_PATH
    dic=json.load(open(db_path,'r',encoding='utf-8'))
    return dic


#===============>access.log
[2017-10-21 19:08:20,285][MainThread:10900][task_id:core.src][src.py:19][INFO][登入成功]
[2017-10-21 19:08:32,206][MainThread:10900][task_id:core.src][src.py:19][INFO][登入成功]
[2017-10-21 19:08:37,166][MainThread:10900][task_id:core.src][src.py:24][ERROR][使用者名稱不存在]
[2017-10-21 19:08:39,535][MainThread:10900][task_id:core.src][src.py:24][ERROR][使用者名稱不存在]
[2017-10-21 19:08:40,797][MainThread:10900][task_id:core.src][src.py:24][ERROR][使用者名稱不存在]
[2017-10-21 19:08:47,093][MainThread:10900][task_id:core.src][src.py:24][ERROR][使用者名稱不存在]
[2017-10-21 19:09:01,997][MainThread:10900][task_id:core.src][src.py:19][INFO][登入成功]
[2017-10-21 19:09:05,781][MainThread:10900][task_id:core.src][src.py:24][ERROR][使用者名稱不存在]
[2017-10-21 19:09:29,878][MainThread:8812][task_id:core.src][src.py:19][INFO][登入成功]
[2017-10-21 19:09:54,117][MainThread:9884][task_id:core.src][src.py:19][INFO][登入成功]