1. 程式人生 > >函數名的運用、閉包以及叠代器

函數名的運用、閉包以及叠代器

readlines 行操作 找到 關閉 什麽是 sprint state error 證明

函數名的運用

函數名是一種特殊的變量,函數名加上括號後表示函數執行,除此之外,函數名還可以進行如下幾條操作:

1. 作為變量賦值

1 def func():
2     print(666)
3 
4 
5 f1 = func
6 f2 = f1
7 f2()   # 打印666

2. 作為容器內數據類型的元素

應用場景:需要調用多個函數的時候

方法一:

 1 def func1():
 2     print(111)
 3 
 4 
 5 def func2():
 6     print(222)
 7 
 8 
 9 def func3():
10     print(333)
11 12 13 func_list = [func1, func2, func3] 14 for func in func_list: 15 func()

執行結果

111
222
333

方法一裏面是按照順序執行函數,如果我們指定要執行某些函數呢,來看方法二

方法二:

 1 def func1():
 2     print(111)
 3 
 4 
 5 def func2():
 6     print(222)
 7 
 8 
 9 def func3():
10     print(333)
11 
12 
13 dic = {
14     1: func1,
15 2: func2, 16 3: func3 17 } 18 19 for k in dic.keys(): 20 dic[k]()

把函數放在字典裏,想執行哪個就執行哪個

3. 作為函數的參數

1 def func(f):
2     f()
3 
4 
5 def func1():
6     print(222)
7 
8 
9 func(func1)    # 222

4. 作為函數的返回值

1 def func(x):
2     return x
3 
4 
5 def func1():
6     print("in func1")
7
8 9 func(func1)() # in func1

實際上,函數名是第一類對象,第一類對象的特點:

1. 可在運行期間創建

2. 可用作函數參數或返回值

3. 可存入變量的實體

籠統來說,第一類變量就是普通變量

閉包

首先拋出一個問題:為什麽要有閉包?來看兩個例子

實例一:

 1 def func(step):
 2     num = 1
 3     num += step
 4     print(num)
 5 
 6 
 7 j = 0
 8 while j < 5:
 9     func(3)
10     j += 1

執行結果

4
4
4
4
4

上面是沒有使用閉包的情況,下面來看使用閉包的情況

實例二:

 1 def wrapper(step):
 2     num = 1
 3 
 4     def inner():
 5         nonlocal num    # 引用num,與return inner形成閉包
 6         num += step     # 此時num在inner執行完了之後不會被清理,會作為下一次的起始值
 7         print(num)
 8     return inner
 9 
10 
11 f = wrapper(3)
12 j = 0
13 while j < 5:
14     f()
15     j += 1

執行結果

4
7
10
13
16

首先來看函數的結構,這是一個嵌套函數,wrapper函數裏面嵌套一個inner函數,要知道,在函數外面是不能直接調用內層函數的,那麽我們怎麽做呢?答案是通過return,我們可以把外層函數的返回值設置為內層函數名,這樣層層返回,就可以在外界調用任意位置的內層函數。可是這和閉包又有什麽關系呢?當然有啦,比如說我想調用inner函數對num進行操作,那麽我是不是得讓inner的外層函數wrapper的返回值設置為inner啊,其次還得在inner內部設置nonolocal,要不然就不能對num就行操作,其實在內層函數引用外層函數的變量,然後外層函數返回內層函數這樣就形成了閉包,總結一下,關於閉包:

1. 內層函數對外層函數(非全局)變量的引用

2. 閉包只存在於內層函數中

3. 函數都要逐層返回,最終返回給最外層函數

下面來看一個例子,

1 def func(n):  # 相當於n=name
2     def inner():
3         print(n)
4 
5     return inner
6 
7 
8 name = "Hanser"
9 f = func(name)

這個是不是閉包呢,答案是肯定的,這裏的n傳入func裏面後是存放於func的名稱空間裏的,inner的print(n)就是內層函數對外層函數的引用,然後func的返回值是內層函數名inner,所以這個是閉包。這樣判斷是不是有點麻煩啊,那麽有沒有簡單的辦法呢,有的,python提供了判斷閉包的方法: __closure__,來看代碼

 1 def func1(a):
 2     n = 1
 3 
 4     def func2():
 5         nonlocal n
 6         n += a
 7 
 8         def func3():
 9             nonlocal n
10             n *= a
11             print(n)
12         return func3
13     return func2
14 
15 
16 f = func1(3)  # f = func2
17 print(f.__closure__[0].cell_contents)    # 獲取引用的外層函數的變量,如果能獲取到,就是閉包
18 print(f.__closure__[1].cell_contents)    #
19 # print(f.__closure__[2].cell_contents)    # 報錯,沒有第三個
20 
21 print("------我是華麗麗的分割線-------")
22 
23 f1 = func1(3)()     # f1 = func3
24 print(f1.__closure__[0].cell_contents)
25 print(f1.__closure__[1].cell_contents)
26 # print(f1.__closure__[2].cell_contents)     # 報錯,沒有第三個

執行結果

3
1
------我是華麗麗的分割線-------
3
4

總結一下,判斷步驟:

(1)找到要判斷的內層函數(line16和line23)

(2)獲取該內層函數引用的外層函數的變量

(3)能獲取到,是閉包;否則不是閉包

這裏有一個小知識點:獲取到的引用的外層函數的變量順序傳入的參數------->外層函數定義的變量

閉包作用

正常程序執行時,遇到函數,隨著函數的結束而關閉臨時名稱空間,閉包的本質就是閉包會創建一個空間,這個空間不會隨著函數的結束而關閉,因而之後可以繼續調用,這是非常有用的,閉包的引用場景:

1. 裝飾器

2. 爬蟲

來看一個爬蟲實例

 1 from urllib.request import urlopen
 2 
 3 
 4 def but():
 5     content = urlopen("https://book.douban.com/annual/2018?source=navigation#1").read()    # 獲取網頁源代碼
 6 
 7     def get_content():
 8         return content
 9     return get_content
10 
11 
12 fn = but()
13 print(id(fn()))    # 2505706231536
14 print(id(fn()))    # 2505706231536  兩個id一樣,證明第一次獲取的內容沒有消失,第二次是直接調用第一次的內容
15 content1 = fn()   # 獲取內容
16 print(content1.decode("utf-8"))   # 解碼

執行結果

2505706231536
2505706231536
<!doctype html>

<html lang="zh-cmn-Hans">

    <head>
        <meta charset="utf-8">
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover">
        <meta name="apple-mobile-web-app-capable" content="yes">
        <link rel="shortcut icon" href="https://img3.doubanio.com/favicon.ico">
        <meta name="format-detection" content="telephone=no">
        <meta name="url_name" content="book_annual2018">
        <meta name="user_id" content="">
        <meta property="og:site_name" content="豆瓣" />
        <meta property="og:title" content="豆瓣2018年度讀書榜單" />
        <meta property="og:description" content="這一年不可錯過的好書都在這裏了" />
        <meta property="og:url" content="https://book.douban.com/annual/2018?source=broadcast" />
        <meta property="og:image" content="https://img3.doubanio.com/img/files/file-1545618075.jpg" />
        <title>豆瓣2018年度讀書榜單</title>
        <script>
            window.ITHIL = {};
            ITHIL.isFrodo = False === True;
            ITHIL.isWechat = False === True;
        </script>
        <script>
            var _hmt = _hmt || [];
            (function() {
                var hm = document.createElement("script");
                var hash = 2018 === 2018 ? 6e5dcf7c287704f738c7febc2283cf0c : 16a14f3002af32bf3a75dfe352478639
                hm.src = "https://hm.baidu.com/hm.js?" + hash;
                var s = document.getElementsByTagName("script")[0]; 
                s.parentNode.insertBefore(hm, s);
            })();
        </script>
    </head>
    <body>
        <div id="app"></div>
        <script src="https://img3.doubanio.com/f/ithil/31683c94fc5c3d40cb6e3d541825be4956a1220d/js/lib/es5-shim.min.js"></script>
        <script src="https://img3.doubanio.com/f/ithil/a7de8db438da176dd0eeb59efe46306b39f1261f/js/lib/es6-shim.min.js"></script>
            <script src="https://img3.doubanio.com/dae/cdnlib/libs/jweixin/1.0.0/jweixin.js"></script>
                <script src="https://img3.doubanio.com/f/ithil/b92012acc8222b31e7f1307c154fdb90b56d64d1/gen/ithil2018.bundle.js"></script>
            <div alt="main-pic" style="display: none">
                <img type="hidden" alt="cover" src="https://img3.doubanio.com/img/files/file-1545618075.jpg">
            </div>
    </body>
</html>

content內容不會隨著but函數的結束而消失,這個非常有用,因為獲取內容後還要進行篩選等操作,而請求一次因為網絡延時等原因是非常耗時間的,有了閉包,就不用再次去請求獲取內容,節省了很多時間。

叠代器

在講叠代器之前,先來看一下可叠代對象,什麽是可叠代對象呢,可叠代對象的定義是:內部含有__iter__方法的對象。

判斷方法

方法一:

s1 = "hello"
print(dir(s1))   # 返回的方法裏面有__iter__()就是可叠代對象
print("__iter__" in dir(s1))   # True

執行結果

[__add__, __class__, __contains__, __delattr__, __dir__, __doc__, __eq__, __format__, __ge__, __getattribute__, __getitem__, __getnewargs__, __gt__, __hash__, __init__, __init_subclass__, __iter__, __le__, __len__, __lt__, __mod__, __mul__, __ne__, __new__, __reduce__, __reduce_ex__, __repr__, __rmod__, __rmul__, __setattr__, __sizeof__, __str__, __subclasshook__, capitalize, casefold, center, count, encode, endswith, expandtabs, find, format, format_map, index, isalnum, isalpha, isdecimal, isdigit, isidentifier, islower, isnumeric, isprintable, isspace, istitle, isupper, join, ljust, lower, lstrip, maketrans, partition, replace, rfind, rindex, rjust, rpartition, rsplit, rstrip, split, splitlines, startswith, strip, swapcase, title, translate, upper, zfill]
True

f = open("goods.txt", encoding="utf-8", mode="r")
print(dir(f))
print("__iter__" in dir(f))

執行結果

[_CHUNK_SIZE, __class__, __del__, __delattr__, __dict__, __dir__, __doc__, __enter__, __eq__, __exit__, __format__, __ge__, __getattribute__, __getstate__, __gt__, __hash__, __init__, __init_subclass__, __iter__, __le__, __lt__, __ne__, __new__, __next__, __reduce__, __reduce_ex__, __repr__, __setattr__, __sizeof__, __str__, __subclasshook__, _checkClosed, _checkReadable, _checkSeekable, _checkWritable, _finalizing, buffer, close, closed, detach, encoding, errors, fileno, flush, isatty, line_buffering, mode, name, newlines, read, readable, readline, readlines, seek, seekable, tell, truncate, writable, write, writelines]
True

方法二:

1 from collections import Iterable
2 from collections import Iterator
3 l1 = [1, 2, 3]
4 print(isinstance(l1, Iterable))  # 判斷是否是可叠代對象
5 print(isinstance(l1, Iterator))  # 判斷是否是叠代器

執行結果

True
False

那麽什麽是叠代器呢,叠代器在可叠代對象的基礎上增加了__next__方法(取值用),可叠代對象可以轉化成叠代器,你猜的沒錯,就是用__iter__方法

1 obj = s1.__iter__()    # 方法一:可叠代對象轉化成叠代器
2 # obj = iter(s1)         # 方法二:可叠代對象轉化成叠代器
3 print(obj.__next__())   # a
4 print(obj.__next__())   # b
5 print(obj.__next__())   # c
6 print(obj.__next__())   # d
7 print(obj.__next__())   # 報錯 StopIteration

執行結果

a
b
c
d

分析上述代碼可以發現規律,__next__方法每次只取一個值,當取值個數超出時會報錯,此外__next__()方法可以用next()方法代替。

1 s2 = [1, 2, 3]
2 obj = s2.__iter__()
3 print(obj.__next__())
4 print(next(obj))   # 與__next__一樣

執行結果

1
2

運用叠代器和while循環還可以模擬for循環,來看代碼

1 lst = [1, 2, 3, 4, 5]
2 obj = iter(lst)
3 while True:
4     try:
5         print(next(obj))
6     except StopIteration:
7         break

執行結果

1
2
3
4
5

試試字典

1 dic = {"name": "hanser", "age": 18, "height": 148}
2 obj = iter(dic)
3 while True:
4     try:
5         print(next(obj))
6     except StopIteration:
7         break

執行結果

name
age
height

對字典直接取值取出的是key,如果想取value或者鍵值對把dic換成dic.values()或dic.items()就行

1 dic = {"name": "hanser", "age": 18, "height": 148}
2 obj = iter(dic.values())
3 while True:
4     try:
5         print(next(obj))
6     except StopIteration:
7         break

執行結果

hanser
18
148

總結一下:

可叠代對象:內部含有__iter__方法的對象

  str, list, tuple, dic, set, range(), 文件句柄都是可叠代對象

叠代器:內部含有__iter__方法和__next__方法的對象

  str, list, tuple, dic, set, range()不是叠代器

  文件句柄是叠代器

判斷可叠代對象和叠代器的方法

  "__iter__" in dir(s), "__next__" in dir(s)

  isinstance(s, Iterable), isinstance(s, Iterator)

可叠代對象轉化成叠代器

  __iter__(), iter()

叠代器特點

  節省內存,叠代器只存放下一個對象的值

  惰性機制,next一次取一個值

  單項取值,不走回頭路

利用叠代器和while循環可以模擬for循環

函數名的運用、閉包以及叠代器