【函數】0四、裝飾器

1、高階函數python

  python中函數是一等對象(first class);函數也是對象,而且它能夠像普通對象同樣複製、做爲參數、做爲返回值。
編程

  返回函數或者參數是函數的函數就是高階函數,也被稱爲函數式編程
bash

In [23]: def counter(base):
    ...:     def inc(x=1):
    ...:         nonlocal base
    ...:         base += x
    ...:         return base
    ...:     return inc          # 返回一個函數
    ...: 

In [24]: inc = counter(3)

In [25]: inc
Out[25]: <function __main__.counter.<locals>.inc>

In [26]: inc()
Out[26]: 4

In [27]: inc()
Out[27]: 5


內置函數sorted()的實現:閉包

In [48]: def sort(it, cmp=lambda a, b: a<b):
    ...:     ret = []
    ...:     for x in it:
    ...:         for i, e in enumerate(ret):
    ...:             if cmp(x, e):
    ...:                 ret.insert(i, x)
    ...:                 break
    ...:         else:
    ...:             ret.append(x)
    ...:     return ret
    ...: 

In [49]: sort([1, 3, 5, 2, 4, ])
Out[49]: [1, 2, 3, 4, 5]

In [50]: sort([1, 3, 5, 2, 4, ], lambda a, b: a>b)
Out[50]: [5, 4, 3, 2, 1]


總結:
app

  函數做爲返回值經常使用於閉包的場景:須要封裝一些變量ssh

  函數做爲參數經常使用於大多數邏輯固定,少部分邏輯不固定的場景ide

  函數做爲參數,返回值也爲是函數:經常使用於做爲參數的函數在執行先後須要一些額外的操做(增長功能)函數式編程


2、裝飾器函數

一、裝飾器
工具

  函數的參數是一個函數,返回值也是一個函數,就能夠做爲裝飾器

In [77]: def add(x, y):
    ...:     return x + y
    ...: 

In [78]: add.__name__
Out[78]: 'add'


In [80]: def logger(fn):
    ...:     def wrap(*args, **kwargs):
    ...:         print('call {}'.format(fn.__name__))
    ...:         ret = fn(*args, **kwargs)
    ...:         print('{} called'.format(fn.__name__))
    ...:         return ret
    ...:     return wrap
    ...: 

In [81]: loged_add = logger(add)

In [82]: loged_add(3, 5)
call add
add called
Out[82]: 8

簡單、優雅一點:

In [90]: def add(x, y):
    ...:     return x + y
    ...: 

In [91]: def logger(fn):
    ...:     def wrap(*args, **kwargs):
    ...:         print('call {}'.format(fn.__name__))
    ...:         ret = fn(*args, **kwargs)
    ...:         print('{} called'.format(fn.__name__))
    ...:         return ret
    ...:     return wrap
    ...: 

In [92]: add = logger(add)    # add變量名被從新賦值定義,賦值是先執行右邊的部分

In [93]: add(3, 4)
call add
add called
Out[93]: 7

更簡單、優雅:

In [102]: def logger(fn):
     ...:     def wrap(*args, **kwargs):
     ...:         print('call {}'.format(fn.__name__))
     ...:         ret = fn(*args, **kwargs)
     ...:         print('{} called'.format(fn.__name__))
     ...:         return ret
     ...:     return wrap
     ...: 

In [103]: @logger # @這是裝飾器的語法糖,至關於 add=logger(add)=wrp
     ...: def add(x, y):
     ...:     return x + y
     ...: 

In [104]: add(1, 2)
call add
add called
Out[104]: 3

這是logger就是裝飾器了


3、裝飾器的反作用

  被裝飾的函數函數名發生了改變

In [73]: add.__name__   # 被裝飾的函數函數名改變了
Out[73]: 'wrap'

In [74]: help(add)

Help on function wrap in module __main__:

wrap(*args, **kwargs)

python是自文檔語言:

In [78]: def fn():
    ...:     print('hello')
    ...:     

In [79]: help(fn)

Help on function fn in module __main__:

fn()
(END) 


In [82]: def fn():
    ...:     '''this is fn'''
    ...:     print('hello')
    ...:     
 

In [83]: help(fn)

Help on function fn in module __main__:

fn()
    this is fn
(END) 


In [84]: fn.__doc__
Out[84]: 'this is fn'

In [85]: fn.__name__
Out[85]: 'fn'

In [86]: dir(fn)      # 查看全部的屬性和方法
Out[86]: 
['__annotations__',
 '__call__',
 '__class__',
 '__closure__',
 '__code__',
 '__defaults__',
 '__delattr__',
 '__dict__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__get__',
 '__getattribute__',
 '__globals__',
 '__gt__',
 '__hash__',
 '__init__',
 '__init_subclass__',
 '__kwdefaults__',
 '__le__',
 '__lt__',
 '__module__',
 '__name__',
 '__ne__',
 '__new__',
 '__qualname__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__']

解決辦法:

In [87]: def logger(fn):
    ...:     def wrap(*args, **kwargs):
    ...:         print('call {}'.format(fn.__name__))
    ...:         ret = fn(*args, **kwargs)
    ...:         print('{} clled'.format(fn.__name__))
    ...:         return ret
    ...:     wrap.__name__ = fn.__name__
    ...:     wrap.__doc__ = fn.__doc__
    ...:     return wrap
    ...: 

In [88]: @logger
    ...: def add(x, y):
    ...:     return x+y
    ...: 

In [89]: add.__name__
Out[89]: 'add'

In [90]: add(1, 2)
call add
add clled
Out[90]: 3

能夠將解決辦法寫成一個函數:

def copy_proprities(src, dst):
    dst.__name__ = src.__name__
    dst.__doc__ = src.__doc__


# 這個函數能夠寫成裝飾器

In [91]: def copy_proprities(src):
    ...:     def _copy(dst):
    ...:         dst.__name__ = src.__name__
    ...:         dst.__doc__ = src.__doc__
    ...:     return _copy
    ...: 

    
# 在裝飾器函數中調用它
In [92]: def logger(fn):
    ...:     def wrap(*args, **kwargs):
    ...:         print('call {}'.format(fn.__name__))
    ...:         ret = fn(*args, **kwargs)
    ...:         print('{} clled'.format(fn.__name__))
    ...:         return ret
    ...:     copy_proprities(fn)(wrap)
    ...:     return wrap
    ...: 

In [93]: @logger
    ...: def add(x, y):
    ...:     return x+y
    ...: 

In [94]: add.__name__
Out[94]: 'add'

In [95]: add(1, 2)
call add
add clled
Out[95]: 3

將解決方案寫成一個裝飾器:

In [102]: def copy_proprities(src):
     ...:     def _copy(dst):
     ...:         dst.__name__ = src.__name__
     ...:         dst.__doc__ = src.__doc__
     ...:         return dst
     ...:     return _copy
     ...: 

In [103]: def logger(fn):
     ...:     @copy_proprities(fn)         
     ...:     def wrap(*args, **kwargs):
     ...:         print('call {}'.format(fn.__name__))
     ...:         ret = fn(*args, **kwargs)
     ...:         print('{} clled'.format(fn.__name__))
     ...:         return ret
     ...:     return wrap
     ...: 

In [104]: @logger          # add=logger(fn)=wrap
     ...: def add(x, y):
     ...:     return x+y
     ...: 

In [105]: add.__name__
Out[105]: 'add'

In [106]: add(1,3)
call add
add clled
Out[106]: 4


實際上標準中已經實現了這個功能:

In [119]: import functools

In [120]: def logger(fn):
     ...:     @functools.wraps(fn)
     ...:     def wrap(*args, **kwargs):
     ...:         print('call {}'.format(fn.__name__))
     ...:         ret = fn(*args, **kwargs)
     ...:         print('{} clled'.format(fn.__name__))
     ...:         return ret
     ...:     return wrap
     ...: 

In [121]: @logger
     ...: def add(x, y):
     ...:     return x+y
     ...: 

In [122]: add.__name__
Out[122]: 'add'


4、類型提示

  類型提示是python3新增的功能

python是動態類型語言,一個變量的類型,是在代碼運行時決定的

一個變量的類型在應用的生命週期中是可變的

In [4]: a = 1

In [5]: type(a)
Out[5]: int

In [6]: a = 'xxj'

In [7]: type(a)
Out[7]: str

在函數中:

In [8]: def add(x, y):
   ...:     return x+y
   ...: 
   
   
# 對於這個函數咱們可能會這樣調用,由於咱們不知道它須要的參數類型

In [9]: add("a", "b")
Out[9]: 'ab'

In [10]: add(1, 2)
Out[10]: 3

In [11]: add('a', 2)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-11-fc7c9413859a> in <module>()
----> 1 add('a', 2)

<ipython-input-8-aa3fbbe3d526> in add(x, y)
      1 def add(x, y):
----> 2     return x+y

TypeError: must be str, not int



In [12]: help(add)    # 幫助文檔中也沒有說明須要什麼類型的參數


Help on function add in module __main__:

add(x, y)
(END)

解決辦法:

In [15]: def add(x, y):
    ...:     '''x + y
    ...:     @param x int
    ...:     @param y int
    ...:     @return int
    ...:     '''
    ...:     return x + y
    ...: 

    
In [17]: help(add)

Help on function add in module __main__:

add(x, y)
    x + y
    @param x int
    @param y int
    @return int
(END)

這裏可能會有以下問題:

  並非每一個人都會記得寫文檔

  文檔並不能保證和代碼一塊兒更新

  文檔是天然語言,不方便機器操做


因此在Python3中新增了類型提示這個特性:

In [18]: def add(x: int, y:int) -> int:
    ...:     return x + y
    ...: 

In [19]: add(1, 2)
Out[19]: 3

In [20]: help(add)

Help on function add in module __main__:

add(x:int, y:int) -> int
(END) 

In [31]: add.__annotations__
Out[31]: {'return': int, 'x': int, 'y': int}

類型提示(類型註解),只是一個註釋,不會做任何檢查

In [22]: add('a', 'b')   # 不檢查,不轉換;就只是一個註釋
Out[22]: 'ab'

但在一些IDE工具中會有警告信息

因此類型註解能夠提供給第三方工具使用(IDE, 靜態分析),或者在運行時獲取信息

在python3.5以前,類型註解只能用在函數參數和返回值

相關文章
相關標籤/搜索