天天看點

協程函數、面向過程程式設計、子產品與包

協程函數、面向過程程式設計、子產品與包

一、協程函數

1 協程函數的定義

協程函數就是使用了yield表達式形成的生成器

執行個體:

def eater(name):
    print('%s eat food' %name)
    while True:
        food = yield
    print('done')

g = eater('Yim')
print(g)

#執行結果:
<generator object eater at 0x000000000227FEB8>                  #證明g現在是一個生成器函數      

2 協程函數的指派

第一階段:next()初始化函數,讓函數暫停在yield位置

第二階段:send()給yield傳值

next()和send()都是讓函數在上次暫停的位置繼續執行。next是讓函數初始化,send會給yield指派并觸發下一次代碼執行

執行個體:

def eater(name):
    print('%s start to eat food' %name)
    while True:
        food = yield
        print('%s eat %s' %(name,food))

e = eater('Yim')

next(e)                             #初始化,等同于e.send(None)
e.send('米飯')                      #給yield傳值
e.send('大餅')

#執行結果
Yim start to eat food
Yim eat 米飯
Yim eat 大餅      
def eater(name):
    print('%s start to eat food' %name)
    food_list = []
    while True:
        food = yield food_list
        food_list.append(food)
        print('%s eat %s' %(name,food))

e = eater('Yim')

next(e)                  #初始化
print(e.send('米飯'))          #1、給yield傳值   2、繼續往下執行,直到再次碰到yield,然後暫停并且把yield後的傳回值當做本次調用的傳回值
print(e.send('大餅'))

#執行結果
Yim start to eat food
Yim eat 米飯
['米飯']
Yim eat 大餅
['米飯', '大餅']      

3 用裝飾器初始化協程函數

def init(func):
    def wrapper(*args,**kwargs):
        res = func(*args,**kwargs)
        next(res)
        return res
    return wrapper

@init
def eater(name):
    print('%s start to eat food' %name)
    food_list = []
    while True:
        food = yield food_list
        food_list.append(food)
        print('%s eat %s' %(name,food))

e = eater('Yim')
print(e.send('米飯'))
print(e.send('大餅'))      

二、 面向過程程式設計

面向過程的程式設計思想:流水線式的程式設計思想,在設計程式時,需要把整個流程設計出來

優點:

  體系結構更清晰

  簡化程式的複雜度

缺點:

  可擴充性差

應用場景:

  Linux核心、git、httpd、shell腳本

執行個體:

# grep -rl 'error' /dir

import os

#初始化裝飾器
def init(func):
    def wrapper(*args,**kwargs):
        g=func(*args,**kwargs)
        next(g)
        return g
    return wrapper

#1、找到所有檔案的絕對路徑 --os.walk
@init
def search(target):
    while True:
        filepath=yield
        g=os.walk(filepath)
        for pardir,_,files in g:
            for file in files:                      #對最後一個元素進行周遊,這些都是檔案
                abspath=r'%s\%s' %(pardir,file)
                target.send(abspath)

#2、打開檔案 --open
@init
def opener(target):
    while True:
        abspath=yield                           #  接收search傳遞的路徑
        with open(abspath,'rb') as f:
            target.send((abspath,f))            # send多個用元組的方式,為了把檔案的路徑傳遞下去

#3、循環讀取每一行内容 --for line in f
@init
def cat(target):
    while True:
        abspath,f=yield #(abspath,f)
        for line in f:
            res=target.send((abspath,line))     # 同時傳遞檔案路徑和每一行的内容
            if res:break

#4、過濾關鍵字 --if 'error' in line
@init
def grep(pattern,target):           # # patter是過濾的參數
    tag=False                       #為了去掉重複的檔案路徑,因為一個檔案中存在多個error關鍵字
    while True:
        abspath,line=yield tag
        tag=False
        if pattern in line:
            target.send(abspath)    # 傳遞有相應内容的檔案路徑
            tag=True

#5、列印該行屬于的檔案名
@init
def printer():
    while True:
        abspath=yield
        print(abspath)

g = search(opener(cat(grep('os'.encode('utf-8'), printer()))))
g.send(r'F:\Python\Code')      

三、函數的遞歸調用

在調用一個函數的過程中,直接或間接的調用了函數本身,就叫遞歸調用

執行有兩個階段:遞推和回溯

遞歸的效率低,需要在進入下一次遞歸時保留目前的狀态,解決方法是尾遞歸,即在函數的最後一步(而非最後一行)調用自己,但是python又沒有尾遞歸,且對遞歸層級做了限制

1. 必須有一個明确的結束條件

2. 每次進入更深一層遞歸時,問題規模相比上次遞歸都應有所減少

3. 遞歸效率不高,遞歸層次過多會導緻棧溢出(在計算機中,函數調用是通過棧(stack)這種資料結構實作的,每當進入一個函數調用,棧就會加一層棧幀,每當函數傳回,棧就會減一層棧幀。由于棧的大小不是無限的,是以,遞歸調用的次數過多,會導緻棧溢出)

#直接
def func():
    print('from func')
    func()

func()

 
#間接
def foo():
    print('from foo')
    bar()

def bar():
    print('from bar')
    foo()

foo()      
# salary(5)=salary(4)+300
# salary(4)=salary(3)+300
# salary(3)=salary(2)+300
# salary(2)=salary(1)+300
# salary(1)=100
#
# salary(n)=salary(n-1)+300     n>1
# salary(1) =100                       n=1

def salary(n):
    if n == 1:
        return 100
    return salary(n-1)+300

print(salary(5))          

四、子產品

如果你從 Python 解釋器退出再進入,那麼你定義的所有的方法和變量就都消失了。為此 Python 提供了一個辦法,把這些定義存放在檔案中,為一些腳本或者互動式的解釋器執行個體使用,這個檔案被稱為子產品。

子產品是一個包含所有你定義的函數和變量的檔案,其字尾名是.py。子產品可以被别的程式引入,以使用該子產品中的函數等功能。這也是Python标準庫的方法

1 import語句

import加載的子產品分為四個通用類别:

  1. 使用Python編寫的代碼(.py檔案)
  2. 已被編譯為共享庫或DLL的C或C++擴充
  3. 包好一組子產品的包
  4. 使用C編寫并連結到Python解釋器的内置子產品

import文法:

       import module1[, module2[,... moduleN]

當解釋器遇到import語句,如果子產品在目前的搜尋路徑就會被導入,搜尋路徑是一個解釋器會先進行搜尋的所有目錄的清單。如想要導入子產品 support,需要把指令放在腳本的頂端:

support.py 檔案代碼為:

def print_func( par ):
    print ("Hello : ", par)
  return      

test.py引入support子產品:

import support              #導入子產品

support.print_func('Yim')   #現在可以調用子產品裡包含的函數了

#執行結果
Hello :  Yim      

還可以給子產品起一個别名:

import support as s1

s1.print_func('Yim')      

一個子產品隻會被導入一次,不管你執行了多少次import。這樣可以防止導入子產品被一遍又一遍地執行(可以使用sys.modules檢視驗證):

import support
import support
import support

support.print_func('Yim')

#執行結果:
Hello :  Yim      

2 from…import*語句

優點:使用源檔案内的名字時無需加字首,使用友善

缺點:容易與目前檔案的名稱空間内的名字混淆

把一個子產品的所有内容全都導入到目前的命名空間也是可行的,隻需使用如下聲明:

from modname import *               #不推薦使用      

下面隻導入support子產品裡面的print_func函數:

from support import print_func      #可以用逗号分隔,寫上多個函數名

print_func('Yim')                   #直接執行print_func函數      

也可以起别名:

from support import print_func as p1

p1('Yim')      

可以使用__all__來控制*,修改support.py檔案:

__all__ = [print_func]                  #可以在清單内添加多個函數名

def print_func( par ):
    print ("Hello : ", par)
    return      

test.py檔案如下:

from support import *

print_func('Yim')                       #執行結果報錯,NameError: name 'print_func' is not defined      

3 子產品的搜尋路徑

搜尋路徑是由一系列目錄名組成的,Python解釋器就依次從這些目錄中去尋找所引入的子產品

搜尋路徑是在Python編譯或安裝的時候确定的,安裝新的庫應該也會修改。搜尋路徑被存儲在sys子產品中的path變量

子產品的查找順序是:記憶體中已經加載的子產品->内置子產品->sys.path路徑中包含的子產品

需要特别注意的是:我們自定義的子產品名不應該與系統内置子產品重名

import sys

print(sys.path)

#執行結果:
['F:\\Python\\Code\\子產品', 'F:\\Python', 'C:\\Python36\\python36.zip', 'C:\\Python36\\DLLs', 'C:\\Python36\\lib', 'C:\\Python36', 'C:\\Python36\\lib\\site-packages']      

在腳本中修改sys.path,引入一些不在搜尋路徑中的子產品

import sys

print(sys.path)

sys.path.append('F:\\Python\\Code\\不常用子產品')
# sys.path.insert(0,'F:\\Python\\Code\\不常用子產品')        #也可以使用insert,排在前的目錄會優先被搜尋

print(sys.path)

#執行結果
['F:\\Python\\Code\\子產品', 'F:\\Python\\2017-18s', 'C:\\Python36\\python36.zip', 'C:\\Python36\\DLLs', 'C:\\Python36\\lib', 'C:\\Python36', 'C:\\Python36\\lib\\site-packages']
['F:\\Python\\Code\\子產品', 'F:\\Python\\2017-18s', 'C:\\Python36\\python36.zip', 'C:\\Python36\\DLLs', 'C:\\Python36\\lib', 'C:\\Python36', 'C:\\Python36\\lib\\site-packages', 'F:\\Python\\Code\\不常用子產品']      

以下是官方文檔:

#官網連結:https://docs.python.org/3/tutorial/modules.html#the-module-search-path
搜尋路徑:
當一個命名為spam的子產品被導入時
    解釋器首先會從内模組化塊中尋找該名字
    找不到,則去sys.path中找該名字

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

注意:在支援軟連接配接的檔案系統中,執行腳本所在的目錄是在軟連接配接之後被計算的,換句話說,包含軟連接配接的目錄不會被添加到子產品的搜尋路徑中

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

4 __name__屬性

一個子產品被另一個程式第一次引入時,其主程式将運作。如果我們想在子產品被引入時,子產品中的某一程式塊不執行,我們可以用__name__屬性來使該程式塊僅在該子產品自身運作時執行。

if __name__ == '__main__':                      #檔案當做腳本運作時,__name__ 等于'__main__'
   print('程式自身在運作')
else:
   print('我來自另一子產品')

#執行結果
程式自身在運作       
#互動式,導入子產品
>>> import support
我來自另一子產品      

說明: 每個子產品都有一個__name__屬性,當其值是'__main__'時,表明該子產品自身在運作,否則是被引入。

常用執行個體:

def func1():
    print('from func1')

def func2():
    print('from func2')

if __name__ == '__main__':
   func1()
   func2()      

五、包

包是一種管理 Python 子產品命名空間的形式,采用"點子產品名稱",比如一個子產品的名稱是 A.B, 那麼他表示一個包 A中的子子產品 B

無論是import形式還是from...import形式,凡是在導入語句中(而不是在使用時)遇到帶點的,這都是關于包才有的導入文法

包是目錄級的(檔案夾級),檔案夾是用來組成py檔案(包的本質就是一個包含__init__.py檔案的目錄)

強調:

  1. 在python3中,即使包下沒有__init__.py檔案,import 包仍然不會報錯,而在python2中,包下一定要有該檔案,否則import 包報錯
  2. 建立包的目的不是為了運作,而是被導入使用,記住,包隻是子產品的一種形式而已,包即子產品

有一個包結構如下:

glance/                     #頂層包
├── __init__.py        #初始化 glance 包
├── api
│   ├── __init__.py
│   ├── policy.py      

導入包裡的子子產品:

import glance.api.policy      

必須使用全名去通路:

glance.api.policy.func()      

還有一種導入子產品的方法是:

from glance.api import policy               #從子產品中導入一個指定的部分到目前命名空間中      

它不需要那些冗長的字首,是以可以這樣使用:

policy.func()      

還有一種就是直接導入一個函數或者變量:

from glance.api.policy import func      

可以直接使用func()函數:

func()      

1 導入包執行個體

包結構如下:

glance/                     #頂層包
run.py                      # 執行檔案
├── __init__.py        #初始化 glance 包
├── api
│   ├── __init__.py
│   ├── policy.py           

導入包:

#1、run.py用全名通路func(),内容如下:
import glance
glance.api.policy.func()
# glance/__init__.py如下:
import glance.api                           #也可以用from glance import api
# glance/api/__init__.py如下:
import glance.api.policy                    #也可以用from glance.api import policy

#2、run.py直接通路func(),内容如下:
import glance
glance.func()
# glance/__init__.py如下:
from glance.api.policy import func          #不能用import glance.api.policy.func
# glance/api/__init__.py可以不用寫      

當使用from package import item這種形式的時候,對應的item既可以是包裡面的子子產品(子包),或者包裡面定義的其他名稱,比如函數,類或者變量。

import文法會首先把item當作一個包定義的名稱,如果沒找到,再試圖按照一個子產品去導入。如果還沒找到,恭喜,一個:exc:ImportError 異常被抛出了。

反之,如果使用形如import item.subitem.subsubitem這種導入形式,除了最後一項,都必須是包,而最後一項則可以是子產品或者是包,但是不可以是類,函數或者變量的名字。

注意事項:

  1. 關于包相關的導入語句也分為import和from ... import ...兩種,但是無論哪種,無論在什麼位置,在導入時都必須遵循一個原則:凡是在導入時帶點的,點的左邊都必須是一個包,否則非法。可以帶有一連串的點,如item.subitem.subsubitem,但都必須遵循這個原則
  2. 對于導入後,在使用時就沒有這種限制了,點的左邊可以是包,子產品,函數,類(它們都可以用點的方式調用自己的屬性)
  3. 對比import item 和from item import name的應用場景:如果我們想直接使用name那必須使用後者。

2 絕對導入和相對導入

以上面的例子是絕對導入的方式

絕對導入:以glance作為起始

相對導入:用.或者..的方式作為起始(隻能在一個包中使用,不能用于不同目錄内)

執行個體:

run.py直接通路func(),内容如下:
import glance
glance.func()
# glance/__init__.py如下:
from .api.policy import func        #1、這樣的好處是:就算包名glance被更改也不會有影響,隻需改run.py内的import …2、..表示上級目錄,可以導入上級目錄中的其他子產品      

特别需要注意的是:

  • 可以用import導入内置或者第三方子產品(已經在sys.path中),但是要絕對避免使用import來導入自定義包的子子產品(沒有在sys.path中),應該使用from... import ...的絕對或者相對導入
  • 包的相對導入隻能用from的形式

六、軟體開發規範

協程函數、面向過程程式設計、子產品與包
bin:存放執行腳本
conf:存放配置檔案
core:存放核心邏輯
db:存放資料庫檔案
lib:存放自定義的子產品與包
log:存放日志      

start.py内容:

import os
import sys

# print(os.path.abspath(__file__))                       #擷取目前檔案的絕對路徑,F:\soft\bin\test.py
# print(os.path.dirname(os.path.abspath(__file__)))       #擷取目前檔案所在的目錄,F:\soft\bin
# print(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))      #擷取目前檔案所在的目錄的上級目錄,F:\soft

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(BASE_DIR)                                   #将soft這個包的路徑加到環境變量

from core import core
    ……                      #導入其他子產品

if if __name__ == '__main__':
    ……                      #執行      

posted on 2017-08-02 19:36 似水_流年 閱讀( ...) 評論( ...) 編輯 收藏

轉載于:https://www.cnblogs.com/yanmj/p/7275650.html

繼續閱讀