python基礎知識縱覽(下)

keithl發表於2017-10-09

python函式

1.函式作用

  • 最大化程式碼重用和最小化程式碼冗餘
  • 流程的分解

2.函式基本概念

  • def建立物件並賦值給某一變數
## 建立一個函式物件並賦值給fn_name
def fn_name(args1,args2,...,argsN):
    <statements>複製程式碼
  • def是可執行的程式碼
## 通過if語句判斷來做定義函式,def是實時執行的
if test:
    def fn_name():
        <statement>
else:
    def fn_name():
        <statement>

## 分配函式物件
myFn = fn_name

## 呼叫函式
fn_name()複製程式碼
  • return將結果物件傳送給呼叫者
## 函式主體一般都包含return語句
def fn_name(args1,args2,...,argsN):
    ...
    return <value>複製程式碼
  • lambda建立一個物件並將結果返回
## 生成函式物件的表達形式
lambda argument1, argument2,... argumentN : expression using arguments

## lambda注意點
其一,lambda是表示式而不是語句
其二,lambda的主體是一個單個表示式而非語句

## 定義一個正常的函式
def func(x,y,z):
    return x+y+z

## 使用lambda表示式
f = lambda x,y,z:x+y+z

## 使用預設引數
f = lambda x=1,y=2,z=3:x+y+z複製程式碼
  • yield向呼叫者發回一個結果物件並記住離開的地方

生成器函式

## 編寫常規def語句並用yield語句一次返回一個結果,在每個結果之間掛起並繼續他們的狀態
## 定義生成函式
def gensquare(N):
    for index in range(N):
        yield index ** 2        

## 等價於以下的函式
def gensquare(N):
     yield 0 ** 2           ## 函式每次遇到一個yield便會向呼叫者傳送一個返回值並掛起
     ...
     yield (N-1) ** 2

## yield是傳送資料不是返回資料
## 呼叫生成函式,此時的函式是可迭代,可迭代物件定義了一個__next__方法
for i in gensquare(5):      
    print(i,end=":")        

0 : 1 : 4 : 9 : 16 :複製程式碼

生成器表示式

## 列表解析表示式
>>> list = [x**2 for x in range(6)]     
[0, 1, 4, 9, 16, 25]

## 生成器表示式類似上述的列表解析但返回的結果是一個物件而不是一個列表
>>> genrator = (x**2 for x in range(6))
<generator object <genexpr> at 0x1021088e0>

## 執行生成器
>>> next(my_generator)

>>> next(my_generator)
1
...

## 編寫一個列表解析器等同於在一個list內建呼叫中包含一個生成器表示式以迫使其一次生成列表中的所有結果
>>> my_list = list(x**2 for x in range(6))複製程式碼
  • global宣告函式中模組級別的變數並進行賦值操作

全域性變數

## 全域性變數是位於模組檔案內部的頂層的變數名
X = 80

## 全域性變數如果是在函式內被賦值的話,必須經過宣告
def chang_x():
    ## 必須宣告
    global X
    X = 90

## 全域性變數在函式的內部不經過宣告也可以被引用
def reference_x():
    print(X)

## 注意:不同的python檔案(模組)之間不要使用『模組物件.屬性名』對全域性變數進行修改,最好的方式通過函式修改
## a.py
X = 99
def change_x(new):
    global X
    X= new

## b.py
import a
a.change_x(97)

## 訪問全域性變數的方式
## test.py
var = 99

def local(): 
    var = 0     ## 外面宣告的var與函式內沒關係,當這個函式執行完畢後,var仍然是99

def glob1(): 
    global var  ## 告知函式中var是屬於全域性變數,直接從全域性作用域開始查詢,若找不到便會到內建作用域查詢,如果還找不到將報錯
    var += 1    

def glob2(): 
    import dir1.module  ## dir1與test.py位於同一個目錄下,module是dir1下的一個模組,var是module下的全域性變數
    dir1.module.var += 1

def glob3(): 
    import sys
    glob = sys.modules['module']    ## 從搜尋路徑中獲取模組,並對該模組全域性變數進行操作
    glob.var += 1複製程式碼
  • nolocal宣告將要賦值的一個封閉的函式變數,即內嵌一個函式
## 基礎語法
def func():
    nonlocal name1, name2, ... # OK here

## nonlocal名稱只能存在於巢狀的def語句中,不能出現在模組的全域性作用域或def之外的內建作用域
def tester(start): 
    state = start               ## 資料儲存在tester函式物件之中
    def nested(label):          ## 返回內嵌的函式物件並且攜帶了外部函式物件的屬性,每次呼叫將改變外部函式物件的屬性state
        nonlocal state          ## 使用nonlocal宣告state,state必須是在巢狀函式nested提前定義過
        print(label, state)
        state += 1              
    return nested

>>> F = tester(0) 
>>> F('spam') 
spam 0
>>> F('ham')
ham 1
>>> F('eggs')
eggs 2複製程式碼
  • 函式引數是通過賦值(物件引用)傳遞的
    • 不可變引數通過"值"傳遞
    • 可變物件通過"指標"進行傳遞
## 引數傳遞是通過自動將物件賦值給本地變數名來實現的
def changer(a,b):
    a = 9               # a是值傳遞,屬於當前函式的本地變數
    b[-1] = "spam"      # b是可變物件通過指標傳遞

## 在函式內部的引數名的賦值不會影響呼叫者
## 改變函式的可變引數的值也許會對呼叫者有影響
def changer(a,b,c):
    a = 9                   ## 本地變數的值傳遞不影響呼叫者
    b[-1] = "spam"          ## 函式改變可變物件所指向的內容值
    c = c[:]                ## 函式內部拷貝副本,不會對呼叫者影響

## 阻止可變物件在函式改變內容值
- 使用拷貝
- 轉成不可變物件,如tuple(list)複製程式碼
  • 引數、返回值以及變數不需要在函式中宣告
## python函式沒有型別約束,可以傳遞或返回任意型別引數
def add(a):
    return a ** 2

>>> add(3)
9
>>> add("xiao")
xiaoxiao複製程式碼

python賦值引數匹配順序

  • 位置:從左至右匹配非關鍵字引數
def func(a,b,c):
    print a,b,c

>>> func(1,2,3)
1,2,3複製程式碼
  • 關鍵字引數:通過匹配變數名稱分配關鍵字引數,與位置無關
def func(a,b,c):
    print a,b,c

>>> func(c=3,a=2,b=1)
2,1,3複製程式碼
  • 其他額外的非關鍵字引數分配到*name元組中
## 任意非關鍵字引數
def func(*args):
    print(args)     ## 傳遞進來是元組資料並賦值變數名稱為args

## 呼叫
>>> f1(29,34,4,3,12,13)
29,34,4,3,12,13,複製程式碼
  • 其他額外的關鍵字引數分配到**name字典中
## 任意關鍵字引數
def func(**args):
    for key,value in args.items():
        print(key +"-->" + value)

## 呼叫
>>> f2(name="xiaoxiao",url="https://www.baidu.com")
url--https://www.baidu.com
name--xiaoxiao複製程式碼
  • 使用預設值分配給在頭部未得到分配的引數
## 函式定義預設引數值
## 以下函式在定義引數傳遞的時候就已經錯誤,自然呼叫就失敗
def fn(name="xiao",age):
    print("the name is "+name+",and the age is "+age)

>>> fn(34)      ## 呼叫失敗
SyntaxError: non-default argument follows default argument

>>> fn(age=34)   ## 呼叫失敗
SyntaxError: non-default argument follows default argument

## 正常的定義方式是沒有指定預設引數值在前,有預設引數值的定義在後
def fn(age,name="xiao"):
    print("the name is "+name+",and the age is "+age)

>>> fn(34)          ## 呼叫正常
>>> fn(age=34)      ## 呼叫正常複製程式碼
python模組與包

1.模組

模組組成

  • import:使匯入者以一個整體獲取模組
  • from:允許客戶端從一個模組中獲取特定的變數名
  • imp.reload:在中止py程式中,提供一種重新載入模組檔案程式碼的方法

模組扮演的角色

  • 程式碼重用
  • 系統名稱空間的劃分
  • 實現共享服務和資料

import在模組第一次匯入時執行三個步驟

  • 找到模組檔案,即搜尋模組
  • 編譯成位碼
  • 執行模組的程式碼來建立所定義的物件

sys.path:即模組搜尋路徑

  • 程式的主目錄
  • PYTHONPATH目錄
  • 標準連結庫目錄
  • 任何.pth檔案的內容

模組編寫

  • import將整個模組物件賦值給一個變數名
  • from將一個或多個變數名賦值給另一個模組中同名的物件
## 相同主目錄
## module1.py   
def check(num):
    return num>0

## module2.py
import module1
module1.check(9)

## from:把模組檔案中的一個或者多個變數名從中複製到當前引用的作用域內,此時無需再通過模組呼叫
from module1 import check
check(9)

## from *:把模組檔案中所有定義好的變數名複製到當前引用的作用域中
from moudle1 import *
check(9)複製程式碼

from與import對等性

from module import name1,name2 
等效於
import module
name1 = module.name1
name2 = moudle.name2
del module複製程式碼

模組檔案生成名稱空間

  • 模組語句在首次匯入時執行
  • 頂層的賦值語句會建立模組屬性
  • 模組的名稱空間能通過屬性__dict__或dir(module)獲取
  • 模組是一個獨立作用域(本地變數就是全域性變數)

過載模組:python內建函式reload()

  • reload會在模組當前名稱空間內執行模組檔案的新程式碼
  • 檔案中頂層賦值語句會使得變數名換成新值
  • 過載會影響所有使用import讀取模組的客戶端
  • 過載只會對以後使用from的客戶端造成影響
## 使用reload()的時候,模組是預先載入過的
/main
    /dir1
        __init__.py
        /dir2
            __init__.py
            dir2module.py
    test.py

## main的主目錄載入到搜尋路徑中
>>> import dir1.dir2.dir2module
dir1 init.....
dir2 init....
dir2 module py ...

>>> reload(dir1.dir2.dir2module)    ## 重新載入dir2module,而不會重新載入dir1和dir2的初始化操作
dir2 module py ...

## 重新載入dir1和dir2
>>> reload(dir1) 
>>> reload(dir1.dir2)複製程式碼

2.包

包的匯入

  • 每一個python模組包都必須含有__init__.py檔案
  • 增加主目錄到包的搜尋路徑中,即PYTHONPATH或者是.pth檔案中
  • 模組搜尋路徑的專案提供平臺特定的目錄路徑字首,之後再在import的路徑左邊新增這些路徑

包的執行

  • 包的初始化:匯入某個目錄時,會自動執行改目錄下__init__.py檔案中的所有程式程式碼
  • 模組名稱空間的初始化:匯入後會變成真實的巢狀物件路徑
  • from 語句的行為:可以在__init__.py定義目錄以from 語句形式匯入時,需要匯出什麼
## 當前目錄結構:
dir0
    /dir1
        __init__.py
        a.py
        /dir2
            __init__.py
            b.py
    /module2
        __init__.py
            /module3
                __init__.py
                b.py
    test.py         
dir0稱為主目錄(__init__.py可有可無),dir1 和 dir2 是模組包,將主目錄新增到搜尋路徑中

## 常規匯入
>>> import dir1.dir2.b      ## 匯入後會執行並返回一個模組物件
dir1 init.....              ## dir1下的__init__.py
dir2 init....               ## dir2下的__init__.py
dir2 module py ...          ## dir2下的b.py

## 使用from匯入
>>> from dir1.dir2 import b     ##  避免每次讀取時重新輸入路徑
dir1 init.....              ## dir1下的__init__.py
dir2 init....               ## dir2下的__init__.py
dir2 module py ...          ## dir2下的b.py複製程式碼

相對包匯入作用域

  • 相對匯入適用於只在包內匯入
  • 相對匯入只是用於from語句
  • 術語含糊不清
## 可以使用from語句前面的點號(".")來指定,匯入相對於外圍的包,
## 這樣的匯入只是在包內部搜尋而非在搜尋路徑(sys.path)搜尋
## 目錄結構
dir1
    dir2
        __init__.py
        a.py
    test.py

## test.py下
from .dir2 import a  ## 和test.py相同包路徑下的dir2資料夾的a模組的匯入複製程式碼

模組查詢總結

  • 簡單模組通過搜尋sys.path路徑列表上每個目錄查詢,從左至右
  • 包是帶有一個特殊的__init__.py檔案的Python模組的直接目錄,可以使用A,B,C目錄路徑語法匯入
  • 同一個包檔案中,常規的import語句使用將會通過sys.paths規則搜尋,而包中的匯入使用from語句以及前面的點號,只是檢查包目錄

喜歡可以關注我個人公眾號

相關文章