程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式師世界 >> 編程語言 >  >> 更多編程語言 >> Python

21.Python函數(六)【函數式編程 下半篇】

編輯:Python

目錄:

  • 每篇前言:
  • Python函數(六)
    • 1.1 函數式編程
      • 1.1.1 閉包
      • 1.1.2 裝飾器
      • 1.1.3 偏函數

每篇前言:

  • 作者介紹:【孤寒者】—CSDN全棧領域優質創作者、HDZ核心組成員、華為雲享專家Python全棧領域博主、CSDN原力計劃作者

  • 本文已收錄於Python全棧系列專欄:《Python全棧基礎教程》
  • 熱門專欄推薦:《Django框架從入門到實戰》、《爬蟲從入門到精通系列教程》、《爬蟲高級》、《前端系列教程》、《tornado一條龍+一個完整版項目》。
  • ​本專欄面向廣大程序猿,為的是大家都做到Python從入門到精通,同時穿插有很多很多習題,鞏固學習。
  • 訂閱專欄後可私聊進一千多人Python全棧交流群(手把手教學,問題解答);進群可領取Python全棧教程視頻 + 多得數不過來的計算機書籍:基礎、Web、爬蟲、數據分析、可視化、機器學習、深度學習、人工智能、算法、面試題等。
  • 加入我一起學習進步,一個人可以走的很快,一群人才能走的更遠!

Python函數(六)

1.1 函數式編程

1.1.1 閉包

  • 注意到在上篇文章中的【函數作為返回值】那一節中返回的函數在其定義內部引用了局部變量args,所以,當一個函數返回了一個函數後,其內部的局部變量還被新函數引用,所以,閉包用起來簡單,實現起來可不容易。
  • 另一個需要注意的問題是,返回的函數並沒有立刻執行,而是直到調用了f()才執行。我們來看一個例子:
def count():
fs = []
for i in range(1, 4):
def f():
return i*i
fs.append(f)
return fs
f1, f2, f3 = count()
  • 在上面的例子中,每次循環,都創建了一個新的函數,然後,把創建的3個函數都返回了。
    你可能認為調用f1(),f2()和f3()結果應該是1,4,9,但實際結果是:
>>> f1()
9
>>> f2()
9
>>> f3()
9
  • 全部都是9!原因就在於返回的函數引用了變量i,但它並非立刻執行。等到3個函數都返回時,它們所引用的變量i已經變成了3,因此最終結果為9。
  • 返回閉包時牢記的一點就是:返回函數不要引用任何循環變量,或者後續會發生變化的變量。
  • 如果一定要引用循環變量怎麼辦?方法是再創建一個函數,用該函數的參數綁定循環變量當前的值,無論該循環變量後續如何更改,已綁定到函數參數的值不變:
def count():
fs = []
for i in range(1, 4):
def f(j):
def g():
return j*j
return g
fs.append(f(i))
return fs
>>> f1, f2, f3 = count()
>>> f1()
1
>>> f2()
4
>>> f3()
9
  • 缺點是代碼較長,可利用lambda表達式縮短代碼:
# -*- coding: utf-8 -*-
""" __author__ = 小小明-代碼實體 """
def count():
fs = []
for i in range(1, 4):
f = lambda j: (lambda: j * j)
fs.append(f(i))
return fs
f1, f2, f3 = count()
print(f1(), f2(), f3())

1.1.2 裝飾器

下面講解所使用的完整示例:

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
  • 函數對象有一個__name__屬性,可以拿到函數的名字:
>>> def now():
... print('2015-3-25')
...
>>> now.__name__
'now'
  • 現在,假設我們要增強now()函數的功能,比如,在函數調用前後自動打印日志,但又不希望修改now()函數的定義,這種在代碼運行期間動態增加功能的方式,稱之為“裝飾器”(Decorator)。
  • 本質上,decorator就是一個返回函數的高階函數。所以,我們要定義一個能打印日志的decorator,可以定義如下:
def log(func):
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper
  • wrapper()函數的參數定義是(*args, **kw),因此,wrapper()函數可以接受任意參數的調用。
  • 觀察上面的log,因為它是一個decorator,所以接受一個函數作為參數,並返回一個函數。我們要借助Python的@語法,把decorator置於函數的定義處:
@log
def now():
print('2015-3-25')
  • 把@log放到now()函數的定義處,相當於執行了語句:
now = log(now)
  • 調用now()函數,不僅會運行now()函數本身,還會在運行now()函數前打印一行日志:
>>> now()
call now():
2015-3-25
  • 如果decorator本身需要傳入參數,那就需要編寫一個返回decorator的高階函數,寫出來會更復雜。比如,要自定義log的文本:
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用法如下:
@log('execute')
def now():
print('2015-3-25')
  • 和兩層嵌套的decorator相比,3層嵌套的效果是這樣的:
>>> now = log('execute')(now)

執行結果如下:

>>> now()
execute now():
2015-3-25
  • 以上兩種decorator的定義都沒有問題,但經過decorator裝飾之後的函數,它們的__name__已經從原來的’now’變成了’wrapper’:
>>> now.__name__
'wrapper'
  • Python內置的functools.wraps能達到wrapper.name = func.__name__的效果,所以,一個完整的decorator的寫法如下:

解釋一下就是:Python裝飾器(decorator)在實現的時候,被裝飾後的函數其實已經是另外一個函數了(函數名等函數屬性會發生改變,比如上面你會發現函數名變成了wrapper),為了不影響,Python的functools包中提供了一個叫wraps的decorator來消除這樣的副作用。寫一個decorator的時候,最好在實現之前加上functools的wrap,它能保留原有函數的名稱和函數屬性

import functools
def log(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper
  • 或者針對帶參數的decorator:
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
  • 練習
    設計一個decorator,它可作用於任何函數上,並打印該函數的執行時間,可以計算任何函數執行時間:
# -*- coding: utf-8 -*-
""" __author__ = 小小明-代碼實體 """
import functools, time
def metric(fn):
@functools.wraps(fn)
def wrapper(*args, **kw):
start_time = time.time() * 1000
result = fn(*args, **kw)
run_time = time.time() * 1000 - start_time
print('%s executed in %s ms' % (fn.__name__, run_time))
return result
return wrapper
@metric
def fast(x, y):
time.sleep(0.003)
return x + y
@metric
def slow(x, y, z):
time.sleep(0.1257)
return x * y * z
f = fast(11, 22)
s = slow(11, 22, 33)

1.1.3 偏函數

  • 假設要轉換大量的二進制字符串,每次都傳入int(x, base=2)非常麻煩,於是,我們想到,可以定義一個int2()的函數,默認把base=2傳進去:
def int2(x, base=2):
return int(x, base)
  • functools.partial可以創建一個偏函數,不需要我們自己定義int2(),可以直接使用下面的代碼創建一個新的函數int2:
>>> import functools
>>> int2 = functools.partial(int, base=2)
>>> int2('1000000')
64
>>> int2('1010101')
85
  • 創建偏函數時,實際上可以接收函數對象、*args和**kw這3個參數,當傳入:
int2 = functools.partial(int, base=2)
  • 實際上固定了int()函數的關鍵字參數base,也就是:
int2('10010')
  • 相當於:
kw = {
 'base': 2 }
int('10010', **kw)
  • 當傳入:
max2 = functools.partial(max, 10)
  • 實際上會把10作為*args的一部分自動加到左邊,也就是:
max2(5, 6, 7)
  • 相當於:
args = (10, 5, 6, 7)
max(*args)
  • 結果為10。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式師世界 All Rights Reserved