<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                # 函數 基本上所有的高級語言都支持函數,Python 也不例外。Python 不但能非常靈活地定義函數,而且本身內置了很多有用的函數,可以直接調用。 借助抽象,我們才能不關心底層的具體計算過程,而直接在更高的層次上思考問題。 寫計算機程序也是一樣,函數就是最基本的一種代碼抽象的方式 ## 調用函數 要調用一個函數,需要知道函數的名稱和參數 調用函數的時候,如果傳入的參數數量不對,會報 TypeError 的錯誤, Python 內置的常用函數還包括數據類型轉換函數,比如 int() 函數可以把其他數據類型轉換為整數: ```js >>> int('123') 123 >>> int(12.34) 12 >>> float('12.34') 12.34 >>> str(1.23) '1.23' >>> str(100) '100' >>> bool(1) True >>> bool('') False ``` 函數名其實就是指向一個函數對象的引用,完全可以把函數名賦給一個變量,相當于給這個函數起了一個 “別名”: ```js >>> a = abs # 變量a指向abs函數 >>> a(-1) # 所以也可以通過a調用abs函數 1 ``` ## 定義函數 ### 定義函數 ```js def my_abs(x): if x >= 0: return x else: return -x ``` 函數體內部的語句在執行時,一旦執行到 return 時,函數就執行完畢,并將結果返回。 如果沒有 return 語句,函數執行完畢后也會返回結果,只是結果為 None。return None 可以簡寫為 return。 如果你已經把 my_abs() 的函數定義保存為 abstest.py 文件了,那么,可以在**該文件**的當前目錄下啟動 Python 解釋器,用 from abstest import my_abs 來導入 my_abs() 函數, > 注意 abstest 是文件名(不含.py 擴展名) ```js >>> from abstest import my_abs >>> my_abs(-9) 9 ``` 如果想定義一個什么事也不做的空函數,可以用 pass 語句: ```js def nop(): pass ``` 實際上 pass 可以用來作為占位符,比如現在還沒想好怎么寫函數的代碼,就可以先放一個 pass,讓代碼能運行起來 ### 參數檢查 調用函數時,如果參數個數不對,Python 解釋器會自動檢查出來,并拋出 TypeError,但是如果參數類型不對,Python 解釋器無法幫我們檢查。 我們可以修改一下 my_abs 的定義,對參數類型做檢查,只允許整數和浮點數類型的參數。數據類型檢查可以用內置函數 isinstance() 實現: ```js def my_abs(x): if not isinstance(x, (int, float)): raise TypeError('bad operand type') if x >= 0: return x else: return -x ``` ### 返回多個值 ```js import math def move(x, y, step, angle=0): nx = x + step * math.cos(angle) ny = y - step * math.sin(angle) return nx, ny ``` 然后調用函數 ```python >>> x, y = move(100, 100, 60, math.pi / 6) >>> r = move(100, 100, 60, math.pi / 6) >>> print(r) (151.96152422706632, 70.0) ``` 原來返回值是一個 tuple!但是,在語法上,返回一個 tuple 可以省略括號,而多個變量可以同時接收一個 tuple,按位置賦給對應的值,所以,Python 的函數返回多值其實就是返回一個 tuple,但寫起來更方便。 ## 函數的參數 Python 的函數定義非常簡單,但靈活度卻非常大。除了正常定義的必選參數外,還可以使用默認參數、可變參數和關鍵字參數 ### 默認參數 一是必選參數在前,默認參數在后,否則 Python 的解釋器會報錯(思考一下為什么默認參數不能放在必選參數前面); 二是如何設置默認參數。 當函數有多個參數時,把變化大的參數放前面,變化小的參數放后面。變化小的參數就可以作為默認參數。 使用默認參數有什么好處? 最大的好處是能降低調用函數的難度 > 定義默認參數要牢記一點:默認參數必須指向不變對象! 因為函數在定義的時候,默認參數的值就被計算出來了,如果默認參數指向可變對象,每次調用的時候,默認參數的內容就改變了,不再是定義時的對象 。 比如 ```js def add_end(L=[]): L.append('END') return L ``` 當使用默認參數調用的時候, ```js >>> add_end() ['END'] ``` 但是再次調用的時候: ```js >>> add_end() ['END', 'END'] >>> add_end() ['END', 'END', 'END'] ``` 默認參數明明是 "[]",但是函數每次都記住了上次添加'END'的list了 要修改上面的例子,我們可以用 None 這個不變對象來實現 ```js def add_end(L=None): if L is None: L = [] L.append('END') return L ``` 我們在編寫程序的時候,如果可以設計一個不變對象,就盡量設計成不變對象。 因為不變對象一旦創建,對象內部的數據就不能修改,這樣就減少了由于修改數據導致的錯誤。 此外,由于對象不變,多任務環境下同時讀取對象不需要加鎖,同時讀一點問題都沒有 ### 可變參數 顧名思義,可變參數就是傳輸的參數的個數是可變的。 由于要輸入的參數個數是不確定的,所以可以將參數作為一個list或者tuple傳進來。 ```js def calc(numbers): sum = 0 for n in numbers: sum = sum + n * n return sum >>> calc([1, 2, 3]) 14 ``` 這樣在調用的時候,需要將參數先組裝成list或者tuple,如果利用可變參數,可以將調用方式簡化為: ```js def calc(*numbers): sum = 0 for n in numbers: sum = sum + n * n return sum ``` 與定義一個list參數相比,僅僅在參數前面加了一個*號。 在函數內部,參數numbers仍然被當作一個tuple 如果已經有一個list或者tuple,,要調用一個可變參數怎么半? 可以在傳入的時候,在list前面加一個*號, ```js >>> nums = [1, 2, 3] >>> calc(*nums) 14 ``` ### 關鍵字參數 上面我們講到了可變參數,它允許我們傳入任意個參數,這些參數可以在函數調用的時候自動組裝成一個tuple,適用于可能傳入任意個數個參數的情況。 有的時候,我們還希望參數的關鍵字也不固定,所以可以傳入一個dict,然后在函數內部自動組裝成一個dict ```js def person(name, age, **kw): print('name:', name, 'age:', age, 'other:', kw) ``` 我們可以傳視頻入任意個數的關鍵字 ```js >>> person('Adam', 45, gender='M', job='Engineer') name: Adam age: 45 other: {'gender': 'M', 'job': 'Engineer'} ``` 這樣可以擴展函數的功能,比如說現在在做一個用戶注冊功能,除了用戶名和年齡是必選項,其他的都是可選項,利用關鍵字來定義函數就可以滿足這個要求。 同樣,我們也可以先組裝成一個dict,然后把該dict轉換成關鍵字傳進去。 ```js >>> extra = {'city': 'Beijing', 'job': 'Engineer'} >>> person('Jack', 24, **extra) name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'} ``` > **extra 表示把 extra 這個 dict 的所有 key-value 用關鍵字參數傳入到函數的 **kw 參數,kw 將獲得一個 dict,注意 kw 獲得的 dict 是 extra 的一份拷貝,對 kw 的改動不會影響到函數外的 extra。 ### 命名關鍵字參數 如果要限制關鍵字參數的名字,例如只接受city和job作為關鍵字參數, ```js def person(name, age, *, city, job): print(name, age, city, job) >>> person('Jack', 24, city='Beijing', job='Engineer') Jack 24 Beijing Engineer ``` *號后面的參數被視為命名關鍵字參數。 如果函數定義中已經有了一個可變參數,后面跟著的命名關鍵字參數就不再需要一個特殊分隔符 * 了 ```js def person(name, age, *args, city, job): print(name, age, args, city, job) ``` 使用命名關鍵字參數,需要注意,必須得傳入參數名,如果沒有傳入的話,調用將會報錯。 ```js >>> person('Jack', 24, 'Beijing', 'Engineer') Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: person() takes 2 positional arguments but 4 were given ``` 同樣命名關鍵字參數可以有缺省值 ```js def person(name, age, *, city='Beijing', job): print(name, age, city, job) # 調用的時候可以不傳入有默認值的 city參數 >>> person('Jack', 24, job='Engineer') Jack 24 Beijing Engineer ``` ### 參數的組合 在Python中定義函數,可以用必選參數、默認參數、可變參數、關鍵字參數、命名關鍵字參數 而且這5種參數可以組合使用, 但是需要注意一定的順序:必選參數、默認參數、可變參數、命名關鍵字參數、關鍵字參數 比如 ```js def f1(a, b, c=0, *args, **kw): print('a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw) def f2(a, b, c=0, *, d, **kw): print('a =', a, 'b =', b, 'c =', c, 'd =', d, 'kw =', kw) ``` 在函數調用的時候,Python 解釋器自動按照參數位置和參數名把對應的參數傳進去。 如果通過一個 tuple 和 dict,你也可以調用上述函數: ```js >>> args = (1, 2, 3, 4) >>> kw = {'d': 99, 'x': '#'} >>> f1(*args, **kw) a = 1 b = 2 c = 3 args = (4,) kw = {'d': 99, 'x': '#'} >>> args = (1, 2, 3) >>> kw = {'d': 88, 'x': '#'} >>> f2(*args, **kw) a = 1 b = 2 c = 3 d = 88 kw = {'x': '#'} ``` 對于任意函數,都可以通過類似 func(*args, **kw) 的形式調用它,無論它的參數是如何定義的。 ### 小結 Python 的函數具有非常靈活的參數形態,既可以實現簡單的調用,又可以傳入非常復雜的參數。 默認參數一定要用 **不可變對象**,如果是可變對象,程序運行時會有邏輯錯誤! 要注意定義可變參數和關鍵字參數的語法: *args 是可變參數,args 接收的是一個 tuple; **kw 是關鍵字參數,kw 接收的是一個 dict。 以及調用函數時如何傳入可變參數和關鍵字參數的語法: 可變參數既可以直接傳入:func(1, 2, 3),又可以先組裝 list 或 tuple,再通過 *args 傳入:func(*(1, 2, 3)); 關鍵字參數既可以直接傳入:func(a=1, b=2),又可以先組裝 dict,再通過 **kw 傳入:func(**{'a': 1, 'b': 2})。 使用 *args 和 **kw 是 Python 的習慣寫法,當然也可以用其他參數名,但最好使用習慣用法。 命名的關鍵字參數是為了限制調用者可以傳入的參數名,同時可以提供默認值。 定義命名的關鍵字參數在沒有可變參數的情況下不要忘了寫分隔符 *,否則定義的將是位置參數。 ## 遞歸函數 如果一個函數在內部調用自身本身,這個函數就是遞歸函數。 比如說階乘,使用遞歸方式寫出來就是 ```python def fact(n): if n == 1: return 1 return n * fact(n - 1) ``` 如果我們計算 fact(5),可以根據函數定義看到計算過程如下: ===> fact(5) ===> 5 * fact(4) ===> 5 * (4 * fact(3)) ===> 5 * (4 * (3 * fact(2))) ===> 5 * (4 * (3 * (2 * fact(1)))) ===> 5 * (4 * (3 * (2 * 1))) ===> 5 * (4 * (3 * 2)) ===> 5 * (4 * 6) ===> 5 * 24 ===> 120 如果我們計算 fact(5),可以根據函數定義看到計算過程如下: ```js ===> fact(5) ===> 5 * fact(4) ===> 5 * (4 * fact(3)) ===> 5 * (4 * (3 * fact(2))) ===> 5 * (4 * (3 * (2 * fact(1)))) ===> 5 * (4 * (3 * (2 * 1))) ===> 5 * (4 * (3 * 2)) ===> 5 * (4 * 6) ===> 5 * 24 ===> 120 ``` 遞歸函數的優點在于定義簡單,邏輯清晰。理論上所有的遞歸函數都可以寫成循環的方式,但是循環的方式不如遞歸清晰。 但是使用遞歸函數需要注意棧溢出。 如果遞歸調用次數過多,就會導致棧溢出。 可以使用 **尾遞歸**來進行優化。 也就是說,在函數返回的時候,調用自身本身,并且return語句不能包含表達式。這樣,編譯器會把尾遞歸做優化,不管調用多少次,都只占用一個棧幀 比如說 `fact(n)` ```python def fact(n): return fact_iter(n, 1 ) def fact_iter(num , product): if num == 1: return product return fact_iter ( num -1 , num * product) ``` 可以看到,return fact_iter(num - 1, num * product) 僅返回遞歸函數本身,num - 1 和 num * product 在函數調用前就會被計算,不影響函數調用。 fact(5) 對應的 fact_iter(5, 1) 的調用如下: ```js ===> fact_iter(5, 1) ===> fact_iter(4, 5) ===> fact_iter(3, 20) ===> fact_iter(2, 60) ===> fact_iter(1, 120) ===> 120 ``` 尾遞歸調用時,如果做了優化,棧不會增長,因此,無論多少次調用也不會導致棧溢出。 遺憾的是,大多數編程語言沒有針對尾遞歸做優化,Python 解釋器也沒有做優化,所以,即使把上面的 fact(n) 函數改成尾遞歸方式,也會導致棧溢出。 # 函數式編程 函數是 Python 內建支持的一種封裝,我們通過把大段代碼拆成函數,通過一層一層的函數調用,就可以把復雜任務分解成簡單的任務,這種分解可以稱之為**面向過程的程序設計**。函數就是面向過程的程序設計的基本單元。 而函數式編程——Functional Programming,雖然也可以歸結到面向過程的程序設計,但其思想更接近數學計算。 函數式編程就是一種抽象程度很高的編程范式,純粹的函數式編程語言編寫的函數沒有變量,因此,任意一個函數,只要輸入是確定的,輸出就是確定的,這種純函數我們稱之為沒有副作用。 而允許使用變量的程序設計語言,由于函數內部的變量狀態不確定,同樣的輸入,可能得到不同的輸出,因此,這種函數是有副作用的。 函數式編程的一個**特點**就是,允許把函數本身作為參數傳入另一個函數,還允許返回一個函數! Python 對函數式編程提供部分支持。由于 Python 允許使用變量,因此,Python 不是純函數式編程語言。 ## 高階函數 變量可以指向函數,而函數的參數能接收變量,所以一個函數就可以接收另一個函數作為參數,這種函數就稱之為高階函數。 ### 變量指向函數 對于Python內置的函數 `abs()`,如果只寫 `abs` ```js >>> abs <built-in function abs> ``` 我們可以把函數本身賦給變量 ```js >>> f = abs >>> f <built-in function abs> ``` 這樣變量就指向了函數。 這個時候,可以使用該變量來調用函數 ```js >>> f = abs >>> f(-10) 10 ``` 那么函數名其實就是指向函數的變量。 所以說高階函數指的就是一個函數可以接收另一個函數作為參數。 ```js def add(x, y, f): return f(x) + f(y) add(-5, 6, abs) ``` 把函數作為參數傳入,這樣的函數稱為高階函數,函數式編程就是指這種高度抽象的編程范式。 ### map reduce Python內建了 map()和reduce() map()函數接收兩個參數,一個是函數,一個是Iterable,map將傳入的函數依次作用到序列的每個元素中。并且把新的結果作為新的Iterable返回。 比如我們有一個函數 f (x)=x2,要把這個函數作用在一個 list [1, 2, 3, 4, 5, 6, 7, 8, 9] 上,就可以用 map() 實現如下: ```js f(x) = x * x │ │ ┌───┬───┬───┬───┼───┬───┬───┬───┐ │ │ │ │ │ │ │ │ │ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ [ 1 2 3 4 5 6 7 8 9 ] │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ [ 1 4 9 16 25 36 49 64 81 ] ``` 使用代碼實現: ```js >>> def f(x): ... return x * x ... >>> r = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9]) >>> list(r) [1, 4, 9, 16, 25, 36, 49, 64, 81] ``` map() 傳入的第一個參數是 f,即函數對象本身。由于結果 r 是一個 Iterator,Iterator 是惰性序列,因此通過 list() 函數讓它把整個序列都計算出來并返回一個 list。 同樣,如果我們不使用 `map()`函數,使用循環也可以得到這樣的計算結果,但是使用循環,就不能一眼看明白把 f (x) 作用在 list 的每一個元素并把結果生成一個新的 list map() 作為高階函數,事實上它把運算規則抽象了 我們甚至可以定義更復雜的函數,比如說將list所有數字都轉換成字符串 ```python list(map(str,[1,2,3,4,5])) ``` 再看 reduce. reduce把一個函數作用在一個序列上,這個序列必須接收兩個參數,它會把結果繼續和序列的下一個元素做累積計算。 ```python reduce(f,[x1,x2,x3,x4])=f(f(f(x1,x2),x3),x4) ``` 首先是將f作用到x1和x2上,然后再把得到的值與x3一起帶入f里面。 比如說對一個序列求活,或者把序列變成整數 ```python from functools import reduce def f (x,y): return x*10 + y reduce(fn , [1,3,4,5]) ``` 我們將這個例子稍加改動就可以寫出str轉int的函數。 對于一個str序列里面每一個元素,使用map將其轉換成 int類型。 然后再使用reduce將其歸并到一起。 ```python from functools imprt reduce def char2num(s): digit = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9} return digit[s] def fn (x, y ): return x * 10 +y reduce(fn , map(char2num, "12345")) ``` 可以整理成一個函數,并且使用lambda函數進行簡化 ```python from functools import reduce DIGITS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9} def char2num (s): return DIGITS[s] def str2int(s): return reduce ( lambda x,y: x*10 + y , map (char2num , s)) ``` ### filter 與map()類似,filter()也接收一個函數和一個序列。 filter()把傳入函數依次作用于每一個元素,然后根據返回值是True還是False來決定保留還是丟棄元素 比如說我們可以先定義一個規則 ```python def is_odd(n): return n % 2 == 1 ``` 如果 輸入的n除以2 余數為1 的話,說明n為奇數。 然后使用filter函數將奇數過濾出來 ```python list(filter(is_odd , [1,2,3,4,5,6])) # 結果為[1,3,5] ``` 也可以把一個序列的空字符串刪除掉 ```Python # 因為需要把不為空的過濾出來,所以定義一個函數,如果字符不為空,則返回1 def not_empty(s): return s and s.strip() list(filter(not_empty , ['A',"","B",None])) ``` 我們知道,如果為空,則求他的bool值為0 我們可以使用filter來求素數 計算素數的一個方法是埃氏篩法 第一步,需要列出從2開始所有自然數,構成一個序列 取序列中的第一個數2,它一定是素數,然后把2的倍數給篩選掉 取新序列的第一個數3,然后把3的倍數篩掉。 同理可推其他步驟 我們可以構建一個從3開始的奇數序列 ```python def _odd_iter(): n = 1 while True: n = n + 2 yield n ``` 這是應該 生成器,而且是一個無限序列 然后定義一個篩選器 ```python # 不是n的倍數的值需要被篩選出來 def _not_divisible(n): return lambda x: x % n > 0 ``` 最后定義一個生成器,不斷返回下一個素數 ```pythoh def prime(): yield 2 it = _odd_iter ()#初始序列 while True: n = next (it) # 返回序列中的第一個數 yield n it = filter (_not_divisible(n) , it) ``` 由于 primes() 也是一個無限序列,所以調用時需要設置一個退出循環的條件: ```js # 打印1000以內的素數: for n in primes(): if n < 1000: print(n) else: break ``` ### sorted 排序的核心是比較兩個元素的大小。 如果是數字,則可以直接進行比較,但是如果是字符串呢?直接比較數學上的大小是沒有意義的 ,我們可以使用函數來自定義比較的過程。 sorted() 函數也是一個高階函數,它還可以接收一個 key 函數來實現自定義的排序,例如按絕對值大小排序: ```Python sorted([36, 5, -12, 9, -21], key=abs) ``` 再比如,對于字符串排序,默認情況下,是按照ASCII大小比較的。也就是說 ,有于 'Z' < 'a', 所以大寫字母Z會排在小寫字符a的前面。 但是我們更希望能忽略大小寫 ```python sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower) ``` ## 返回函數 對于一個可變參數的求和 ```js def calc_sum(*args): ax = 0 for n in args: ax = ax + n return ax ``` 但是,如果不需要立刻求和,而是需要在后面的代碼里面根據需要再計算? 可以返回求和的函數,而不是求和的結果 ```python def lazy_sum(*args): def sum(): ax = 0 for n in args: ax = ax + n return ax return sum ``` 當我們調用 lazy_sum() 時,返回的并不是求和結果,而是求和函數: ```js >>> f = lazy_sum(1, 3, 5, 7, 9) >>> f <function lazy_sum.<locals>.sum at 0x101c6ed90> ``` 調用函數 f 時,才真正計算求和的結果: ```python f() 25 ``` 我們在函數 lazy_sum中又定義了一個sum函數,并且內部函數sum可以引用外部函數lazy_sum的參數和局部變量。 同時,當lazy_sum返回函數sum的時候,相關參數和變量都是保存在返回的函數中的,這種程序結構叫做“閉包” 另外返回的函數并沒有立刻執行,而是直到調用了 f() 才執行 ```python def count(): fs = [] for i in range(1, 4): def f(): return i*i fs.append(f) return fs f1, f2, f3 = count() ``` 在上面的函數中,每次循環都創建了一個新的函數。 可能認為調用 f1(),f2() 和 f3() 結果應該是 1,4,9,但實際結果是: ```js >>> f1() 9 >>> f2() 9 >>> f3() 9 ``` 全部都是 9!原因就在于返回的函數引用了變量 i,但它并非立刻執行。等到 3 個函數都返回時,它們所引用的變量 i 已經變成了 3,因此最終結果為 9。 > 返回閉包時牢記一點:返回函數不要引用任何循環變量,或者后續會發生變化的變量。 > 也就是返回的函數的外層不能有循環變量 另外,當我們調用lazy_sum()的時候,每次調用都會返回一個新的函數,這兩個函數的調用結果互不影響。 ```js >>> f1 = lazy_sum(1, 3, 5, 7, 9) >>> f2 = lazy_sum(1, 3, 5, 7, 9) >>> f1==f2 False ``` ## 匿名函數 lambda 匿名函數有個限制,就是只能有一個表達式,不用寫 return,返回值就是該表達式的結果 用匿名函數有個好處,因為函數沒有名字,不必擔心函數名沖突。 此外,匿名函數也是一個函數對象,也可以把匿名函數賦值給一個變量,再利用變量來調用該函數: ## 裝飾器 假設需要增強now函數的功能,比如說在函數調用前自動打印日志,這種在代碼運行期間動態增加功能的方式稱為 “裝飾器” 本質上, decorator就是一個返回函數的高階函數。 ```python def log(func): def wrapper(*args, **kw): print ('call %s():' % func.__name__) return func(*args , **kw) return wrapper ``` 函數其實就是一個對象,同時,函數對象有一個 `__name__`屬性,可以拿到函數的名字 ```python now.__name__ 'now' ``` 觀察上面的 log,因為它是一個 decorator,所以接受一個函數作為參數,并返回一個函數。 那么如何調用呢? 可以把decorator置于函數的定義處 ```python @log def now(): print('2015-03-25') ``` 調用now()函數的時候,不僅會運行函數本身,還會在函數前打日志 ```js >>> now() call now(): 2015-3-25 ``` 把 @log 放到 now() 函數的定義處,相當于執行了語句: ```js now = log(now) ``` 由于 log() 是一個 decorator,返回一個函數,所以,原來的 now() 函數仍然存在,只是現在同名的 now 變量指向了新的函數,于是調用 now() 將執行新函數,即在 log() 函數中返回的 wrapper() 函數。 如果 decorator 本身需要傳入參數,那就需要編寫一個返回 decorator 的高階函數,寫出來會更復雜。比如,要自定義 log 的文本: ```python def log(text): def decorator(func): def wrapper (*args , **kw): print('%s %s():' % (text , func.__name__)) return func(*args , **kw) return wrapper return decorator ``` 這個 3 層嵌套的 decorator 用法如下: ```js @log('execute') def now(): print('2015-3-25') ``` 執行結果如下: ```python >>> now() execute now(): 2015-3-25 ``` 和兩層嵌套的 decorator 相比,3 層嵌套的效果是這樣的: ```python >>> now = log('execute')(now) ``` 首先執行 `log('execute')`,返回的是 decorator函數,再調用返回的函數,參數是now函數,返回的是 wrapper 另外,還需要把原始函數的__name__等屬性復制到 wrapper() 函數中,否則,有些依賴函數簽名的代碼執行就會出錯。 Python 內置的 functools.wraps可以實現 `wrapper.__name__ = func.__name__`這樣的功能。 所以一個完整的decorator的寫法是 ```Python import functools def log (func): @functools.wraps(func) def wrapper(*args, **kw): print ('call %s():' % func.__name__) return func(*args , **kw) return wrapper ``` 或者針對帶參數的decorator ```python import functools def log (text): def decorator(func): @functools.wraps(func) def wrapper(*args , **kw): print ('%s %s():' % (text , func.__name__)) return func(*args , **kw) return wrapper return decorator ``` ## 偏函數 當函數的參數個數太多,需要簡化的時候,可以使用 `functools.partial`可以創建一個新的函數,這個新的函數可以固定住原函數的部分參數,從而在調用的時候更簡單。 這就是 偏函數(partial function) 比如說 int()默認將字符串轉成十進制整數,這里面base參數的值默認為10 但是假設我們需要大量轉換成二進制字符,如果要每次都傳入 int(x , base = 2)非常的麻煩,所以可以再定義一個 `int2` ```python import functools int2 = functools.partial(int , base = 2 ) ``` functools.partial 的作用就是,把一個函數的某些參數給固定住(也就是設置默認值),返回一個新的函數,調用這個新函數會更簡單。 ## 柯里化 最簡單的柯里化 (currying) 指的是將原來接收 2 個參數的函數 f (x, y) 變成新的接收 1 個參數的函數 g (x) 的過程,其中新函數 g = f (y)。 以普通的加法函數為例: ```python def add1(x, y): return x + y ``` 通過嵌套函數可以把函數 add1 轉換成柯里化函數 add2。 ```python def add2(x): def add(y): return x + y return add ``` 仔細看看函數 add1 和 add2 的參數 - add1:參數是 x 和 y,輸出 x + y - add2:參數是 x,輸出 x + y - g = add2(2):參數是 y,輸出 2 + y 下面代碼也證實了上述分析: ```python add1 add2 g = add2(2) g <function __main__.add1(x, y)> <function __main__.add2(x)> <function __main__.add2.<locals>.add(y)> ``` - add1:參數是 x 和 y,輸出 x + y - add2:參數是 x,輸出 x + y - g = add2(2):參數是 y,輸出 2 + y 比較「普通函數 add1」和「柯里化函數 add2」的調用,結果都一樣。 ```python print( add1(2, 3) ) print( add2(2)(3) ) print( g(3) ) 5 5 5 ``` # 解析式 解析式是將一個可迭代對象轉換成另一個可迭代對象的工具。 不嚴謹的說,容器類型數據(str,tuple,list,dict,set)都是可迭代對象。 - 第一個可迭代對象:可以是任意容器類型數據 - 第二個可迭代對象:需要看是什么類型的解析試 - 列表解析式:可迭代對象是list - 字典解析式:可迭代對象是dict - 集合解析式:可迭代對象是set 解析式就是為了把「帶條件的 for 循環」簡化成一行代碼的。 列表解析式整個語句用「中括號 []」框住,而字典和集合解析式整個語句中「大括號 {}」框住。想想 list, dict 和 set 用什么括號定義就明白了 ```python # list comprehension [值 for 元素 in 可迭代對象 if 條件] # dict comprehension {鍵值對 for 元素 in 可迭代對象 if 條件} # set comprehension {值 for 元素 in 可迭代對象 if 條件} ``` 根據 input-operation-output 這個過程總結: - input:任何「可迭代數據 A」 - operation:用 for 循環來遍歷 A 中的每個元素,用 if 來篩選滿足條件的元素 Agood - output:將 Agood 打包成「可迭代數據」,生成列表用 [],生成列表用 {} ## 列表解析式 如何從一個含整數列表中把奇數挑出來? ```python odds = [n * 2 for n in lst if n % 2 == 1] ``` ![UTOOLS1565690873417.png](http://yanxuan.nosdn.127.net/8cb51d3138cc33c794cea4f85a94e292.png) 你可以把「for 循環」到「解析式」的過程想像成一個「復制 - 粘貼」的過程: - 將「for 循環」的新列表復制到「解析式」里 - 將 append 里面的表達式 n * 2 復制到 新列表里 - 復制循環語句 for n in lst 到新列表里,不要最后的冒號 - 復制條件語句 if n%2 == 1 到新列表里,不要最后的冒號 在把上面具體的例子推廣到一般的例子,從「for 循環」到「列表解析式」的過程如下: ![UTOOLS1565690946998.png](http://yanxuan.nosdn.127.net/6496ece04660bb237fdc2c1266d36566.png) 上面「for 循環」只有一層,如果兩層怎么轉換「列表解析式」? 套用一維列表解析式的做法 ![UTOOLS1565691007928.png](http://yanxuan.nosdn.127.net/bf505132d2a62fd387944a74a856e4ab.png) 兩點需要注意: - 該例沒有「if 條件」條件,或者認為有,寫成「if True」。如果有 「if 條件」那么直接加在「內 for 循環」后面。 - 「外 for 循環」寫在「內 for 循環」前面。 ## 其他解析式 可以舉一反三 比如說字典解析式 ![UTOOLS1565691199948.png](http://yanxuan.nosdn.127.net/e8e36c66ab9d9809a6b46c286f52a361.png) ## 小結 再回顧下三種解析式,我們發現其實它們都可以實現上節提到的 filter 和 map 函數的功能,用專業計算機的語言說,解析式可以看成是 filter 和 map 函數的語法糖 - 語法糖 (syntactic sugar):指計算機語言中添加的某種語法,對語言的功能沒有影響,但是讓程序員更方便地使用。 - 語法鹽 (syntactic salt):指計算機語言中添加的某種語法,使得程序員更難寫出壞的代碼。 - 語法糖漿 (syntactic syrup):指計算機語言中添加的某種語法,沒能讓編程更加方便。 為什么說「列表解析式」是 「map/filter」的語法糖,兩者的類比圖如下: ![UTOOLS1565691998196.png](http://yanxuan.nosdn.127.net/230e99c03ac0438fed4aa6fbaa172445.png) 兩者的主要作用就是將原列表根據某些條件轉換成新列表, 再者 - 列表解析式用 if條件來做篩選得到item,然后再用f函數作用到item上 - map/filter:用filter函數做篩選,再用map函數作用在篩選的元素上。 「列表解析式」是種更簡潔的方式。 用「在列表中先找出奇數再乘以 2」,如果用列表解析式來實現 ```python [n * 2 for n in lst if n % 2 == 1] ``` 如果使用 map/filter來實現 ```python list(map(lambda n: n*2 , filter (lambda n : n%2 == 1 , lst))) ``` ## 小例子 用解析試將二維元組里面的每個元素提取出來并存儲到一個列表中 ```python tup = ((1, 2, 3), (4, 5, 6), (7, 8, 9)) ``` 先遍歷第一層元組,用 for t in tup,然后遍歷第二層元組,用 for x in t,提取每個 x 并 " 放在 “列表中,用 [] ```python flattend = [x for t in tup for x in t] ``` ![UTOOLS1565692317359.png](http://yanxuan.nosdn.127.net/d5ab7ae2ac8119007f5ecb4ea7d142a4.png) 如果我們想把上面「二維元組」轉換成「二維列表」呢? ```python [[x for x in t ] for t in tup ] ``` ## 復雜例子 用解析試把不規則的列表a打平 ```python a = [1, 2, [3, 4], [[5, 6], [7, 8]]] ``` 用解析式一步到位解決上面問題有點難,特別是列表 a 不規則,每個元素還可以是 n 層列表,因此我們需要遞推函數 (recursive function),即一個函數里面又調用自己。 ```python def f(x): if type(x) is list: return [ y for l in x for y in f(l)] else: return [x] a = [1, 2, [3, 4], [[5, 6], [7, 8]]] f(a) ``` 把整個列表遍歷一遍,有四個元素, 1, 2, [3,4]和[[5,6],[7,8]] 當x是元素的時候(不是list),返回[x] - f(1)的返回值為[1] - f(2)的返回值為[2] 當x是列表的時候,會執行 `[y for l in x for y in f(l)]` 當 `x=[3,4]` - `for l in x`:指的是x里面每個元素l,那么l遍歷3和4 - `for y in f(l)`:指的是f(l)里面每個元素y - 當l= 3 ,由于是一個元素,所以 `f(l)=[3]`,y遍歷3 - 當l=4,由于是一個元素,那么f(l) = [4],y遍歷4 整個 f ([3 ,4]) 的返回值是 [3 ,4]。同理,當 x = [[5, 6], [7, 8]] 時,f (x) 的返回值是 [5, 6, 7, 8]。 把這所有的 y 再合成一個列表不就是 ```python [1, 2, 3, 4, 5, 6, 7, 8] ``` 再寫成匿名函數 ```python a = [1, 2, [3, 4], [[5, 6], [7, 8]]] f = lambda x: [y for l in x for y in f(l)] if type(y) is list else [x] ``` ![UTOOLS1565694208848.png](http://yanxuan.nosdn.127.net/10191705c204d570c27603f947343fa7.png) # 總結 優雅清晰是 python 的核心價值觀,高階函數和解析式都符合這個價值觀。 函數包括正規函數 (用 def) 和匿名函數 (用 lambda), 函數的參數形態也多種多樣,有位置參數、默認參數、可變參數、關鍵字參數、命名關鍵字參數。 匿名函數主要用在高階函數中,高階函數的參數可以是函數 (Python 里面內置 map/filter/reduce 函數),返回值也可以是參數 (閉包、偏函數、柯里化函數)。 解析式并沒有解決新的問題,只是以一種更加簡潔,可讀性更高的方式解決老的問題。解析式可以把「帶 if 條件的 for 循環」用一行程序表達出來,也可以實現 map 加 filter 的功能。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看