5 Python3 函數進階&迭代器與生成器

一、函數進階

1.一、名稱空間python

又名name space, 顧名思義就是存放名字的地方,存什麼名字呢?舉例說明,若變量x=1,1存放於內存中,那名字x存放在哪裏呢?名稱空間正是存放名字x與1綁定關係的地方算法

名稱空間共3種,分別以下閉包

  • locals: 是函數內的名稱空間,包括局部變量和形參
  • globals: 全局變量,函數定義所在模塊的名字空間
  • builtins: 內置模塊的名字空間

1.二、做用域

  • 全局範圍:全局存活,全局有效
  • 局部範圍:臨時存活,局部有效

查看做用域方法 globals(),locals()app

level = 'L0'
n = 22


def func():
    level = 'L1'
    n = 33
    print(locals())

    def outer():
        n = 44
        level = 'L2'
        print(locals(),n)

        def inner():
            level = 'L3'
            print(locals(),n) #此外打印的n是多少?
        inner()
    outer()
func()

輸出結果

{'level': 'L1', 'n': 33}
{'level': 'L2', 'n': 44} 44
{'level': 'L3', 'n': 44} 44函數

 
LEGB 表明名字查找順序: locals -> enclosing function -> globals -> __builtins__ locals 是函數內的名字空間,包括局部變量和形參
enclosing 外部嵌套函數的名字空間
globals 全局變量,函數定義所在模塊的名字空間
builtins 內置模塊的名字空間

1.三、閉包

關於閉包,即函數定義和函數表達式位於另外一個函數的函數體內(嵌套函數)。並且,這些內部函數能夠訪問它們所在的外部函數中聲明的全部局部變量、參數。性能

當其中一個這樣的內部函數在包含它們的外部函數以外被調用時,就會造成閉包。也就是說,內部函數會在外部函數返回後被執行。測試

而當這個內部函數執行時,它仍然必需訪問其外部函數的局部變量、參數以及其餘內部函數。ui

這些局部變量、參數和函數聲明(最初時)的值是外部函數返回時的值,但也會受到內部函數的影響。spa

def outer():
    name = 'alex'

    def inner():
        print("在inner裏打印外層函數的變量",name)

    return inner


f = outer() 

f()

閉包的意義:返回的函數對象,不只僅是一個函數對象,在該函數外還包裹了一層做用域,設計

這使得,該函數不管在何處調用,優先使用本身外層包裹的做用域

二、裝飾器

有了閉包函數的概念,咱們再去理解裝飾器會相對容易一些。python裝飾器本質上就是一個函數,它可讓其餘函數在不須要作任何代碼變更的前提下增長額外的功能,

裝飾器的返回值也是一個函數對象(函數的指針)。裝飾器函數的外部函數傳入我要裝飾的函數名字,返回通過修飾後函數的名字;內層函數(閉包)負責修飾被修飾函數。

從上面這段描述中咱們須要記住裝飾器的幾點屬性,以便後面能更好的理解:     實質: 是一個函數     參數:是你要裝飾的函數名(並不是函數調用)     返回:是裝飾完的函數名(也非函數調用)     做用:爲已經存在的對象添加額外的功能     特色:不須要對對象作任何的代碼上的變更 python裝飾器有不少經典的應用場景,好比:插入日誌、性能測試、事務處理、權限校驗等。裝飾器是解決這類問題的絕佳設計。

而且從引入中的列子中咱們也能夠概括出:裝飾器最大的做用就是對於咱們已經寫好的程序,咱們能夠抽離出一些雷同的代碼組建多個特定功能的裝飾器

,這樣咱們就能夠針對不一樣的需求去使用特定的裝飾器,這時由於源碼去除了大量泛化的內容而使得源碼具備更加清晰的邏輯。

 

 

帶裝飾器的函數

user_status = False
def
login(func): #把要執行的模塊從這裏傳進來 def inner():#再定義一層函數 _username = "alex" #僞裝這是DB裏存的用戶信息 _password = "abc!23" #僞裝這是DB裏存的用戶信息 global user_status if user_status == False: username = input("user:") password = input("pasword:") if username == _username and password == _password: print("welcome login....") user_status = True else: print("wrong username or password!") if user_status == True: func() # 看這裏看這裏,只要驗證經過了,就調用相應功能 return inner #用戶調用login時,只會返回inner的內存地址,下次再調用時加上()纔會執行inner函數 @login def america(): #login() #執行前加上驗證 print("----歐美專區----")

至關於先          america ==  login(america)---------------------login(func)

括號裏面的   (america)------(func)

返回 inner的內存地址 ----即 american==login(america) = inner------而後加()   inner()---if user_status == True: 執行:func()== america()

 

2.一、兩個裝飾器

 

def w1(func):
    print('---正在裝飾--')
    def inner():
        print('---正在驗證權限1--')
        func()
    return inner


def w2(func):
    print('---正在裝飾2--')
    def inner():
        print('---正在驗證權限2--')
        func()
    return inner

# 只要python解釋器執行到了這個代碼,那麼就會自動的進行裝飾,而不是等到調用的時候才裝飾的
@w2
@w1
def f1():
    print('---f1')

f1()
輸出結果:
---正在裝飾--
---正在裝飾2--
---正在驗證權限2--
---正在驗證權限1--
---f1

 

 在調用f1()函數以前已經進行裝飾了

 2.二、帶參數的裝飾器

exit_flag = False
def login(f):
    def outer(fun):
        def inner(*args):  # 必須和 實參帶參數保持一致   america('kfc')

            if f == 'qq':
                user_info = {'alex':'abc'}
                global exit_flag
                if exit_flag == False:
                    username = input('username:>').strip()
                    password = input('password:>').strip()
                    if username in user_info and password == user_info[username]:
                        print('welcome login...')
                        exit_flag = True

                    else:
                        print('username or password is wrong')
                if exit_flag == True:
                    fun(*args)
        return inner
    return outer

@login('qq')
def america(*args):
    print('welcome to the america')
@login('qq')
def japan(*args):
    print('welcome to the japan')
@login('qq')
def china(*args):
    print('welcome to the china')

america('kfc') #帶參數
japan('daoguo')
china('beautiful')

輸出:‘

username:>alex
password:>abc
welcome login...
welcome to the america
welcome to the japan
welcome to the china

 

帶參數的先執行 參數  login('qq') == login(auth_type)  返回 auth的地址

 

 三、列表生成器

如今有個需求,看列表[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],要求你把列表裏的每一個值加1,你怎麼實現?你可能會想到2種方式

二逼青年版

>>> a
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> b = []
>>> for i in a:b.append(i+1)
... 
>>> b
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> a = b
>>> a
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
普通青年版

a = [1,3,4,6,7,7,8,9,11]

for index,i in enumerate(a):
    a[index] +=1
print(a)
文藝青年版

>>> a
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> a = map(lambda x:x+1, a)
>>> a
<map object at 0x101d2c630>
>>> for i in a:print(i)
... 
3
5
7
9
11

 

列表生成器:

>>> a = [i+1 for i in range(10)] >>> a [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 這樣的寫法就叫作列表生成式

四、生成器

經過列表生成式,咱們能夠直接建立一個列表。可是,受到內存限制,列表容量確定是有限的。
並且,建立一個包含100萬個元素的列表,不只佔用很大的存儲空間,若是咱們僅僅須要訪問前面幾個元素,那後面絕大多數元素佔用的空間都白白浪費了。 因此,若是列表元素能夠按照某種算法推算出來,那咱們是否能夠在循環的過程當中不斷推算出後續的元素呢?
這樣就沒必要建立完整的list,從而節省大量的空間。

在Python中,這種一邊循環一邊計算的機制,稱爲生成器:generator。

 

要建立一個generator,有不少種方法。第一種方法很簡單,只要把一個列表生成式的[]改爲(),就建立了一個generator:

>>> L = [x * x for x in range(10)]
>>> L
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> g = (x * x for x in range(10)) >>> g
<generator object <genexpr> at 0x1022ef630>

 

建立L和g的區別僅在於最外層的[]和(),L是一個list,而g是一個generator。

咱們能夠直接打印出list的每個元素,但咱們怎麼打印出generator的每個元素呢?

若是要一個一個打印出來,能夠經過next()函數得到generator的下一個返回值:
>>> next(g)
0
>>> next(g)
1
>>> next(g)
4

 

咱們講過,generator保存的是算法,每次調用next(g)就計算出g的下一個元素的值,直到計算到最後一個元素,沒有更多的元素時,拋出StopIteration的錯誤。

固然,上面這種不斷調用next(g)實在是太變態了,正確的方法是使用for循環,由於generator也是可迭代對象:

>>> g = (x * x for x in range(10))
>>> for n in g:
...     print(n)

因此,咱們建立了一個generator後,基本上永遠不會調用next(),而是經過for循環來迭代它,而且不須要關心StopIteration的錯誤。

generator很是強大。若是推算的算法比較複雜,用相似列表生成式的for循環沒法實現的時候,還能夠用函數來實現。

好比,著名的斐波拉契數列(Fibonacci),除第一個和第二個數外,任意一個數均可由前兩個數相加獲得:

1, 1, 2, 3, 5, 8, 13, 21, 34, ...

斐波拉契數列用列表生成式寫不出來,可是,用函數把它打印出來卻很容易: 

def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        print(b)
        a, b = b, a + b
        n = n + 1
    return 'done'

4.一、建立生成器的方法

方法1,只要把一個列表生成式的[]改爲(),就建立了一個generator:
方法2:
yield 把函數變成生成器

也就是說,上面的函數和generator僅一步之遙。要把fib函數變成generator,

只須要把print(b)改成yield b就能夠了:

 

def fib(max):
    n,a,b = 0,0,1

    while n < max:
        #print(b)
        yield  b #把函數執行凍結在這一步,而且把b的值,返回給next()
        a,b = b,a+b

        n += 1

    return 'done'
f = fib(10)
print(fib(10))#<generator object fib at 0x0000000001DE3750>
print(next(f))# 1  #另一種表達方式  print(f._next_())
print(next(f))# 1
print(next(f))# 2
while True:
    try:
        x = next(g)
        print('g:',x)
    except StopIteration as e:
        print('generator return value:',e.value)
        break
若是想要拿到返回值,必須捕獲StopIteration錯誤,返回值包含在StopIteration的value中:

 yield返回數據,並凍結當前執行過程    yield b,返回數據的效果至關於print(b)

next:喚醒凍結的執行過程,繼續執行,直到遇到下一個yield

send:一、喚醒並繼續執行,二、發送一個信息到生成器內部

#_*_coding:utf-8_*_
__author__ = 'Alex Li'

import time
def consumer(name):
    print("%s 準備吃包子啦!" %name)
    while True:
       baozi = yield
       print("包子[%s]來了,被[%s]吃了!" %(baozi,name))


def producer(name):
    c = consumer('A')
    c2 = consumer('B')
    c.__next__()
    c2.__next__()
    print("老子開始準備作包子啦!")
    for i in range(10):
        time.sleep(1)
        print("作了2個包子!")
        c.send(i)
        c2.send(i)

producer("alex")

# 經過生成器實現協程並行運算

for i in rang(0,10)

rang也是生成器

>>> range(10)  # 直接生成list
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

打開文件的 f 也是生成器

 

五、迭代器iter()

迭代是Python最強大的功能之一,是訪問集合元素的一種方式。

迭代器是一個能夠記住遍歷的位置的對象。

迭代器對象從集合的第一個元素開始訪問,直到全部的元素被訪問完結束。迭代器只能往前不會後退。

 

迭代器有兩個基本的方法:iter() 和 next()。

 

字符串,列表或元組對象均可用於建立迭代器:

咱們已經知道,能夠直接做用於for循環的數據類型有如下幾種:

一類是集合數據類型,如list、tuple、dict、set、str等;

一類是生成器generator,包括生成器和帶yield的generator function。

這些能夠直接做用於for循環的對象統稱爲可迭代對象:Iterable。

可使用isinstance()判斷一個對象是不是Iterable對象:
>>> from collections import Iterable >>> isinstance([], Iterable) True >>> isinstance({}, Iterable) True >>> isinstance('abc', Iterable) True >>> isinstance((x for x in range(10)), Iterable) True >>> isinstance(100, Iterable) False
*能夠被next()函數調用並不斷返回下一個值的對象稱爲迭代器:Iterator。
生成器都是Iterator對象,但list、dict、str雖然是Iterable(可迭代),卻不是Iterator(迭代器)把list、dict、str等Iterable變成Iterator可使用iter()函數: >>> isinstance(iter([]), Iterator) True >>> isinstance(iter('abc'), Iterator) True 你可能會問,爲何list、dict、str等數據類型不是Iterator? 這是由於Python的Iterator對象表示的是一個數據流,Iterator對象能夠被next()函數調用並不斷返回下一個數據,直到沒有數據時拋出StopIteration錯誤。

能夠把這個數據流看作是一個有序序列,但咱們卻不能提早知道序列的長度,只能不斷經過next()函數實現按需計算下一個數據,因此Iterator的計算是惰性的,只有在須要返回下一個數據時它纔會計算。 Iterator甚至能夠表示一個無限大的數據流,例如全體天然數。而使用list是永遠不可能存儲全體天然數的。 小結 凡是可做用於for循環的對象都是Iterable類型; 凡是可做用於next()函數的對象都是Iterator類型,它們表示一個惰性計算的序列; 集合數據類型如list、dict、str等是Iterable(可迭代的)但不是Iterator(迭代器),不過能夠經過iter()函數得到一個Iterator對象。 Python3的for循環本質上就是經過不斷調用next()函數實現的,例如:
for x in [1, 2, 3, 4, 5]: pass 實際上徹底等價於: # 首先得到Iterator對象: it = iter([1, 2, 3, 4, 5]) # 循環: while True: try: # 得到下一個值: x = next(it) except StopIteration: # 遇到StopIteration就退出循環 break
list = [1,2,34,5]
it = iter(list)
print(next(it))
print(next(it))
print(next(it))

》:1
   2
   34
相關文章
相關標籤/搜索