第4天,函式進階

光銀努力吧發表於2017-05-29

[toc]

一、函式物件

在python中,函式是一等物件,“一等物件”滿足下述條件的程式實體:

  • 可以被引用
  • 可以當作引數傳遞給函式
  • 能做為函式的返回結果
  • 可以當作容器資料型別的元素,例如:可以當作字典中某個key的value
  • 在執行時建立

1.可以被引用

def foo():
    print(`from foo`)

func=foo

print(foo)
print(func)
func()

# 輸出:
<function foo at 0x0000000000BAE1E0>
<function foo at 0x0000000000BAE1E0>
from foo

2. 可以當作引數傳遞給另一個函式

def foo():
    print(`from foo`)

def bar(func):
    print(func)
    func()         # 此時func() == foo()

bar(foo)           #將函式foo記憶體物件當做引數傳給bar()

# 輸出:
<function foo at 0x000000000114E1E0>
from foo

3.可以當作一個函式的返回結果

def foo():
    print(`from foo`)

def bar(func):
    return func

f=bar(foo)       # 此時f = foo

print(f)
f()         # 此時執行f() 相當於執行foo()

# 輸出:
<function foo at 0x0000000000B8E1E0>
from foo

4. 當作其他容器型別資料的元素

def foo():
    print(`from foo`)
    
dic={`func`:foo}   # 定義一個字典dic,將函式foo的記憶體物件做為value

print(dic[`func`])  # 相當於列印foo的記憶體物件
dic[`func`]()       # 相當於執行foo()函式

# 輸出:
<function foo at 0x0000000000A4E1E0>
from foo

應用:

def select(sql):
    print(`========>select`)

def insert(sql):
    print(`========>add`)

def delete(sql):
    print(`=======>delete`)

def update(sql):
    print(`=======>update`)


func_dic={
    `select`:select,
    `update`:update,
    `insert`:insert,
    `delete`:delete
}    # 定義一個字典,將上面定義的函式名做為value

def main():
    while True:
        sql = input(`>>: `).strip()
        if not sql:continue
        l = sql.split()       
        cmd=l[0]
        if cmd in func_dic:
            func_dic[cmd](l)

main()

二、函式巢狀

1. 函式的巢狀定義

簡單理解就是在定義一個函式時,函式體內又定義一個子函式,示例如下:

def f1():
    def f2():
        print(`from f2`)
        def f3():
            print(`from f3`)
        f3()
    f2()
f1()

# 輸出:
from f2
from f3

2. 巢狀函式的呼叫

下面示例是一個求4個數中最大值的小程式:

# 巢狀函式
def mymax(x,y):
    return x if x > y else y

def four(a,b,c,d):
    res1=mymax(a,b)  # 呼叫mymax()函式先比較a和b,較大的值給res1    
    res2=mymax(c,res1)
    res3=mymax(d,res2)
    return res3

print(four(8,45,9,34))

# 輸出:
45

三、名稱空間與作用域

1.定義名字的方法

  • 匯入的模組名稱time,如:import time
  • 定義變數的名稱name,如:name = `caigy`
  • 定義函式的名稱func,如:
def func():
    pass
  • 定義類的名稱foo,如:
class foo:
    pass

2. 三種名稱空間

2.1 內建名稱空間

隨著Python直譯器的啟動而產生,比如python內建的一些函式:sun()、max()、min()等,這些函式隨著python的啟動就定義好了,所以在定義名稱時不要與這些關鍵字重名
可以用以下方法檢視python的內建函式:

import builtins
for i in dir(builtins):
    print(i)
2.2 全域性名稱空間

py檔案的執行會產生全域性名稱空間,指的是檔案級別定義的名字都會放入該空間

2.3 區域性名稱空間

呼叫函式時會產生區域性名稱空間,只在函式呼叫時臨時繫結,函式呼叫結束後解除繫結。

如下示例:

# !/user/bin/env python
# -*- coding:utf-8 -*-

name = `caigy`   # 1

def func():
    x = 1        # 2

程式碼中:

  1. 變數name是全域性名稱空間
  2. 變數x在函式func()中,是區域性名稱空間

3. 作用域

  • 全域性作用域:內建名稱空間、全域性名稱空間
  • 區域性作用域:區域性名稱空間

python中名字的查詢順序:
區域性名稱空間 —> 全域性名稱空間 —> 內建名稱空間

  • 檢視全域性作用域內的名字: globals()
  • 檢視區域性作用域內的名字: locals()

示例程式碼:

name = `caigy`
def foo():
    name = `egon`
    print(name)
    print(locals())
    print(globals())
foo()

以上程式碼輸出結果:

egon
{`name`: `egon`}
{`__file__`: `D:/PycharmProjects/s17/day04/test.py`, `foo`: <function foo at 0x013B5660>, `__loader__`: <_frozen_importlib_external.SourceFileLoader object at 0x01755A90>, `name`: `caigy`, `__name__`: `__main__`, `__spec__`: None, `__package__`: None, `__builtins__`: <module `builtins` (built-in)>, `__doc__`: None, `__cached__`: None}

作用域的有效範圍

  • 全域性作用域的名字:全域性有效,在任何位置都能被訪問到,除非del刪掉,否則會一直存活到檔案執行完畢
  • 區域性作用域的名字:區域性有效,只能在區域性範圍內呼叫,只在函式呼叫時才有效,呼叫結束就失效

注:當全域性變數與區域性變數同名時,在定義區域性變數的子程式內,區域性變數起作用;在其它地方全域性變數起作用

四、閉包函式

閉包函式的特性:

  • 定義在一個函式的內部的函式
  • 包含對外部作用域而非全域性作用域的引用

該內部函式就稱為閉包函式
如下示例:

def func():
    name = `caigy`
    def foo():
        print(name)
    return foo

f = func()  # func()返回結果是foo ,所以f = foo
print(f)    # 列印的實際上是foo的記憶體物件地址
f()

# 輸出結果:
<function func.<locals>.foo at 0x01342150>
caigy

如上述程式碼中,foo()就是一個閉包函式

閉包函式的應用:隨性計算
爬取一個網頁

from urllib.request import urlopen

def index(url):
    def get():
        return urlopen(url).read()
    return get

f= index(`http://www.360duohui.com`)
res = f().decode(`utf-8`)
print(res)

上述程式碼中get()函式就是一個閉包函式

總結:閉包函式實現了內部函式在外部可以被呼叫

獲取閉包函式所引用的外部變數(非全域性變數)的值

def func():
    name=`caigy`
    def foo():
        print(name)
    return foo
f = func()
print(f.__closure__[0].cell_contents)

# 輸出:
caigy

五、裝飾器(decorator)

裝飾器,顧名思義,就是用來裝飾用的,具體點說就是給其它程式新增功能的。其本質就是函式,功能是為其他函式新增新功能。

裝飾器本身可以是任何可呼叫物件,被裝飾的物件也可以是任意可呼叫的物件。

1. 為什麼要用裝飾器呢?

源於程式開的開放封裝原則:

  • 對修改是封閉的,對擴充套件是開放的。
  • 裝飾器就是為了在不修改被裝飾物件的原始碼以及呼叫方式的前提下,為其新增新功能

如下示例:

def run():
    time.sleep(3)
    print(`already test 3s`)
run()

為上述程式碼新增一個列印日誌的功能:
無參裝飾器

import time
def timer(func):
    def wrapper(*args,**kwargs):
        start_time = time.time()
        func()     #3
        stop_time = time.time()
        print(`run is %ss`%(stop_time-start_time))
    return wrapper

timer       #1
def run():
    time.sleep(3)
    print(`already test 3s`)
run()       #2

#1 @timer 實際上就是做了run = timer(run) 的操作;
#2 因為timer()函式的返回值是wrapper,所以此時的run = wrapper , 所run()實際就相當於呼叫了wrapper()
#3 run 做為引數傳給timer()函式,所以此時func=run,此時的func()相當於呼叫了run()

以上程式碼的輸出結果如下:

already test 3s
run is 3.0033679008483887s

2. 附加多個裝飾器

有參裝飾器

import time
def timer(func_1):
    def wrapper_1(*args,**kwargs):
        start_time = time.time()
        func_1(*args,**kwargs)      #3
        stop_time = time.time()
        print(`run is %s秒 `%(stop_time-start_time))
    return wrapper_1

user_stat = {`name`:None,`stats`:False}
def auth(func):
    def wrapper(*args,**kwargs):
        if user_stat[`name`] and user_stat[`stats`]:
            print(`login successful`)
            func(*args, **kwargs)
        else:
            name = input(`name: `)
            pwd = input(`password: `)
            if name == `egon` and pwd == `123`:
                # 登入成功後記錄使用者名稱和登入狀態,下次呼叫時就不用再輸入使用者名稱登入了
                user_stat[`name`] = `egon`
                user_stat[`stats`] = True
                print(`login successful`)
                func(*args,**kwargs)
                # time.sleep(3)
            else:
                print(`login error`)
    return wrapper

@timer   #2
@auth    #1  
def index():
    print(`Welcome to Oldboyedu.com`)

@auth
def home(name):
    print(`Welcome to %s homepage`%name)

index()   #4
home(`egon`)

輸出結果:

name: egon
password: 123
login successful
Welcome to Oldboyedu.com
run is 3.2893900871276855秒 
login successful
Welcome to egon homepage

#1 @auth 實際上就是做了index = auth(index)的操作,因為auth()函式的返回值是wrapper,所以index此時等於wrapper,在下方加括號()就可以直接呼叫wrapper()函式了
#2 由於此時@auth 為wrapper, 所以@timer 就是wrapper = timer(wrapper) , timer()的返回值是wrapper_1,所以此時wrapper = wrapper_1
#3 由於wrapper被當作引數傳給timer(),所以此時func_1 = wrapper
#4 在執行index()時,相於當於執行wrapper_1() ;當程式執行到func_1(args,*kwargs)時,此時的func_1 就是 wrapper,所以就會呼叫wrapper(args,*kwargs);當wrapper()函式內的程式執行到func(args,*kwargs)時,此時的func = index ,所以用呼叫真實定義的index()函式;然後返回wrapper_1(),繼續執行func_1()後的程式碼。

六、迭代器(iterator)

1. 迭代的概念

重複的過程稱為迭代,每次重複即為一次迭代,並且每次迭代的結果作為下一次迭代的初始值;
不是迭代:

while True: #只滿足重複,因而不是迭代
    print(`====>`)

下面才為迭代

l = [1, 2, 3]
count = 0
while count < len(l):  
    print(`====>`, l[count])
    count += 1

# 輸出:
====> 1
====> 2
====> 3

注:以上這種方式迭代的是列表,而列表是有序的物件,那像無序的物件,比如:字典,集合,檔案等如何迭代呢?

2. 為什麼要有迭代器

對於沒有索引的資料型別,必須提供一種不依賴索引的迭代方式
可迭代的物件:內建__iter__方法的物件,都是可迭代的物件

[1,2].__iter__()        # 列表
`hello`.__iter__()      # 字串
(1,2,).__iter__()       # 元組
{`a`:1,`b`:2}.__iter__()    # 字典
{1,2,3}.__iter__()      # 集合

迭代器: 執行__iter__方法,得到的結果就是迭代器,迭代物件都具體__next__方法

i = [1,2,3].__iter__()
print(i)    # 列印可迭代物件的記憶體地址

print(i.__next__())
print(i.__next__())
print(i.__next__())
print(i.__next__())   
    # 超出迭代的物件個數後會丟擲異常:StopIteration

# 輸出:
<list_iterator object at 0x011B5AB0>
1
2
3
Traceback (most recent call last):
  File "D:/PycharmProjects/s17/day04/test.py", line 209, in <module>
    print(i.__next__())
StopIteration

迭代字典

i={`a`:1,`b`:2,`c`:3}.__iter__()

print(i.__next__())     # 迭代字典中的key
print(i.__next__())
print(i.__next__())
print(i.__next__())  
    # 超出字典i中的鍵值對的個數,會丟擲異常:StopIteration

換一種方法:

i={`a`:1,`b`:2,`c`:3}
I = iter(i)

print(next(I))
print(next(I))
print(next(I))

注:__iter__() = iter(),__next__() = next(),兩種方法得到的結果是一樣的。

利用while迴圈迭代字典

dic={`a`:1,`b`:2,`c`:3}
i=dic.__iter__()   # 生成迭代器物件
while True:
    try:
        key=i.__next__()
        print(dic[key])
    except StopIteration:  # 遇到指定異常後,執行子程式碼
        break

3. 如何判斷一個物件是可迭代物件,還是迭代器物件

  • 可迭代物件:只有__iter__方法,執行該方法得到的是迭代器物件
  • 迭代器物件:有__next__方法和__iter__方法,迭代器物件執行__iter__方法,得到的結果仍然是它本身。

判斷可迭代的物件需要匯入Iterable模組

from collections import Iterable,Iterator

f = open(`a.txt`,`w`)
f.__iter__()

# 下列資料型別都是可迭代的物件
print(isinstance(`abc`,Iterable))       # 字串
print(isinstance([1,2,3],Iterable))     # 列表
print(isinstance({`a`:1,},Iterable))    # 字典
print(isinstance({1,2,3},Iterable))     # 集合
print(isinstance((1,2,),Iterable))      # 元組
print(isinstance(f,Iterable))           # 檔案    

# 輸出:
True
True
True
True
True
True

判斷迭代器物件需要匯入Iterator模組

from collections import Iterable,Iterator

f = open(`a.txt`,`w`)
f.__iter__()

# 只有檔案是迭代器物件
print(isinstance(`abc`,Iterator))
print(isinstance([],Iterator))
print(isinstance((),Iterator))
print(isinstance({`a`:1},Iterator))
print(isinstance({1,2},Iterator))
print(isinstance(f,Iterator))

# 輸出:
False
False
False
False
False
True

驗證迭代器對執行__iter__方法得到的結果仍然是其本身

f = open(`a.txt`,`w`)
f1 = f.__iter__()
print(f)
print(f1)
print(f1 is f)      # 輸出`True`表示結論正確 

# 輸出:
<_io.TextIOWrapper name=`a.txt` mode=`w` encoding=`cp936`>
<_io.TextIOWrapper name=`a.txt` mode=`w` encoding=`cp936`>
True

4. 迭代器的優點和缺點

其特點:

  • 訪問者不需要關心迭代器內部的結構,僅需通過next()方法不斷去取下一個內容
  • 不能隨機訪問集合中的某個值 ,只能從頭到尾依次訪問
  • 訪問到一半時不能往回退
  • 便於迴圈比較大的資料集合,節省記憶體

  • 優點:

    • 提供了一種不依賴於下標的迭代方式
    • 就迭代器本身來說,更節省記憶體
  • 缺點:

    • 無法獲取迭代器物件的長度
    • 不如序列型別的資料取值靈活,而且是一次性的,只能往後取值,不能往前退

      驗證:只能往後取值,不能往前退

        l=[10000,2,3,4,5]
        i=iter(l)
        for item in i:
            print(item)
        print(`=====================`)
        
        for item in i:
            print(item)
        
        # 輸出:
        10000
        2
        3
        4
        5
        ====================
        # 通過上述程式碼 的結果可以看出,通過列表l生成的迭代器i ,通過for迴圈迭代後,再次通過for迴圈迭代,就取不到值了

擴充套件:enumerate()方法生成的也是迭代器物件

l=[2,3,4]

i=enumerate(l)

print(next(i))
print(next(i))
print(next(i))

# 輸出:
(0, 2)
(1, 3)
(2, 4)

總結:python中for迴圈就是通過迭代器的方式來實現的,而while只是普通的迴圈,for迭代 == while + try … except(異常處理)

七、生成器(generator)

只要函式體內包含yield關鍵字,該函式就是生成器函式

1. 生成器簡單示例

生成器就是迭代器

def index():
    print(`first`)
    yield 1
    print(`second`)
    yield 2
    print(`third`)
    yield 3

g = index()
for i in g:
    print(i)

上述程式碼輸出:

first
1
second
2
third
3

通過next()方法:

def index():
    print(`first`)
    yield 1
    print(`second`)
    yield 2
    print(`third`)
    yield 3

g = index()
print(next(g))  # next()方法觸發迭代器g的執行,進而觸發函式的執行
print(next(g))
print(next(g))

# 輸出:
first
1
second
2
third
3

生成器應用 :

def counter(n):
    print(`start...`)
    i=0
    while i < n:
        yield i
        i+=1
    print(`end...`)

g=counter(5)
print(g)    # 這一步不會執行counter()函式,如果是普通函式,就會被執行
print(next(g))
print(next(g))
print(next(g))
print(next(g))
print(next(g))

# 輸出:
<generator object counter at 0x0196DB10>
start...
0
1
2
3
4

注:上述程式碼中,迭代器物件g,只有通過next()方法(呼叫時,才會觸發函式counter()的執行

總結:
yield的功能:

  • 相當於為函式封裝好__iter____next__
  • return只返回一次值,函式就終止了,而yield能返回多次值,每次返回都會將函式暫停,下一次next()會從上一次暫停的位置繼續執行

2. 生成器執行流程

def foo():
    print(`in the foo ...`)
    food = yield `您好`
    print(`food >>>`,food)
    print(`我在後面`)
    food1= yield `你壞`
    print(`food1 >>> `,food1)

g= foo()
res = next(g)
print(res)
res1 = g.send(`x`)
print(res1)
##res2= g.send(`xx`)

```
生成器執行流程:
1.g=foo(),只是將foo()生成器物件賦值給g,然後繼續往下執行;

2.遇到next()或g.send(`x`)就開始執行foo()內部的程式碼,
  執行遇到第一個yield時,就暫停(我也理解為進入休眠狀態),
  並將yield後面的值返回給next(g),並跳出到foo()外面next(g)所在的那一行,
  將yield返回的值賦值給res

3.res接收yield返回給next(g)的值,然後往下執行程式碼,列印res的值;

4.當再次遇到next()或g.send(`x`)時,喚醒foo()繼續從上次 
  暫停的位置開始執行, 同時將g.send(‘x’)中的`x`傳送 
  給第一個yield,並賦值給food,然後繼續往下執行;

5.當遇到第二個yield時,進入暫停(休眠),
  同時將yield後面的值返回給g.send(`x`),
  跳出到g.send(`x`)所在的那一行,並將yield返回的值賦值給res1,
  然後繼續執行至結束。
    
注意:
    print(res1)後面沒有程式碼了,此時foo()中的food1是空,
    如果print(res1)後面再出現g.send(`xx`)程式碼,
    才會將`xx`傳送給第二個yield,並賦值給food1;
    但是,foo()內部會從第二個yield那一行繼續往下執行,
    如果後面沒有yield關鍵字了,程式就會丟擲一個StopIteration異常。
```

生成器的應用:
實現Linux命令tail -f a.txt | grep `python`的功能

import time
def tail(filepath):
    with open(filepath,encoding=`utf-8`) as f:
        f.seek(0,2)     # 跳到檔案末尾
        while True:
            line=f.readline().strip()
            if line:
                yield line      #3
            else:
                time.sleep(0.2)

def grep(pattern,lines):        #1
    for line in lines:          #2
        if pattern in line:     #4
            yield line          #5

g=grep(`python`,tail(`a.txt`))
print(g)

for i in g:                     #6
    print(i)

程式解析:
#1 pattern = `python` , lines = tail(`a.txt`)
#2 此時的lines是一個迭代器,經過for迴圈會觸發tail(`a.txt`)函式的執行,這時執行tail()函式內的程式,會通過while迴圈監控檔案末尾是否有內容
#3 如果有新內容,則通過yield返回
#4 第#3通過yield返回的line傳給#4行的line
#5 這裡將line通過yield返回給#6
#6 這時i = line


相關文章