pthon之函數式編程


函數式編程是一種抽象計算的編程范式。

不同語言的抽象層次不同:計算機硬件->匯編語言->C語言->Python語言

                                  指令        ->           ->函數  ->函數式

                                  計算機————————————>計算

函數式編程的特點:把計算視為函數不是指令

純函數式編程:不需要變量,沒有副作用,測試簡單。

支持高階函數,代碼簡單。

python作為一種支持函數式編程的語言的特點:

1.不是純的函數式編程,允許有變量。

2.支持高階函數,函數也可以作為變量輸入

3.支持閉包,有了閉包之后就能夠返回函數。

4.有限度的支持匿名函數

高階函數的特點:

1.變量可以指向函數

>>> abs(-10)
10
>>> abs
<built-in function abs>
>>> f = abs
>>> f(-20)
20

2.函數名其實就是指向函數的一個變量

>>> abs = len
>>> abs([1,2,3])
3

這里面我們將len這個函數名所直接指向的函數賦給了abs,這個時候,abs就能夠計算list的長度了。

高階函數:能夠接收函數做參數的函數

>>> def add(x,y,f):
    return f(x)+f(y)

>>> add(1,-2,abs)
3
>>> add(1,-2,abs())
Traceback (most recent call last):
  File "<pyshell#4>", line 1, in <module>
    add(1,-2,abs())
TypeError: abs() takes exactly one argument (0 given)

在這里就展示了將函數作為參數,這里面我們的函數只能是傳入一個變量名。

map()是 Python 內置的高階函數,它接收一個函數 f 和一個 list,並通過把函數 f 依次作用在 list 的每個元素上,得到一個新的 list 並返回。

>>> def f(x):
    return x*x

>>> print (map(f,[1,2,3,4,5,6,7,8,9]))
<map object at 0x0404C9D0>
>>> print (list(map(f,[1,2,3,4,5,6,7,8,9])))
[1, 4, 9, 16, 25, 36, 49, 64, 81]

這里是python3.0的版本,而3.0的版本在生成list的時候我們就需要加一個list,如果是2.0的版本我們就不要添加一個list了

def f(x):
    return x*x
print map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])
[1, 4, 9, 10, 25, 36, 49, 64, 81]

reduce()函數也是Python內置的一個高階函數。reduce()函數接收的參數和 map()類似,一個函數 f,一個list,但行為和 map()不同,reduce()傳入的函數 f 必須接收兩個參數,reduce()對list的每個元素反復調用函數f,並返回最終結果值。

reduce()還可以接收第3個可選參數,作為計算的初始值。

def f(x, y):
    return x + y
reduce(f, [1, 3, 5, 7, 9], 100)
125

但是在python3.0版本當中就不包含reduce函數了。如果需要使用的話呢就需要使用from functools import reduce.

>>> def f(x,y):
    return x + y

>>> from functools import reduce
>>> reduce(f,[1,3,5,7,9])
25
>>> reduce(f,[1,3,5,7,9],100)
125

filter()函數是 Python 內置的另一個有用的高階函數,filter()函數接收一個函數 f 和一個list,這個函數 f 的作用是對每個元素進行判斷,返回 True或 False,filter()根據判斷結果自動過濾掉不符合條件的元素,返回由符合條件元素組成的新list。

>>> def is_odd(x):
    return x % 2 == 1

>>> list(filter(is_odd,[1,4,6,7,9,12,17]))
[1, 7, 9, 17]

利用filter(),可以完成很多有用的功能,例如,刪除 None 或者空字符串

>>> def is_not_empty(s):
    return s and len(s.strip()) > 0

>>> list(filter(is_not_empty,['test',None,'','str',' ','END']))
['test', 'str', 'END']

s.strip(rm) 刪除 s 字符串中開頭、結尾處的 rm 序列的字符

rm為空時,默認刪除空白符(包括'\n', '\r', '\t', ' ')

 sorted()也是一個高階函數,它可以接收一個比較函數來實現自定義排序,比較函數的定義是,傳入兩個待比較的元素 x, y,如果 x 應該排在 y 的前面,返回 -1,如果 x 應該排在 y 的后面,返回 1。如果 x 和 y 相等,返回 0。

 

>>> sorted([36,5,12,9,21],reverse=True)
[36, 21, 12, 9, 5]

在python3.0中我們進行了改動,只要我們將這個reverse改為True就可以進行倒序的排法,而不需要自己定義新的倒序排法。

由於python3.0和2.0的版本不同,sorted進行了很大的改動。

在Python3.0當中呢我們的比較函數只能有一個函數

>>> def L_upper(x):
    return x.lower()

>>> sorted(['adam','paul','dean'],key=L_upper,reverse=True)
['paul', 'dean', 'adam']

就如這樣,這個key就是前面的list在排序時侯的關鍵字

Python的函數不但可以返回int、str、list、dict等數據類型,還可以返回函數

>>> def f():
    print('call f()...')
    def g():
        print("call g()...")
    return g

>>> x=f()
call f()...
>>> x
<function f.<locals>.g at 0x03FB9FA8>
>>> x()
call g()...

在這里面x便是一個函數,也即是那個函數g()

而在打出x()便是調用函數

這種返回函數的操作可以延緩函數的執行。

>>> def calc_sum(lst):
    def lazy_sum():
        return sum(lst)
    return lazy_sum

>>> f=calc_sum([1,2,3,4])
>>> f
<function calc_sum.<locals>.lazy_sum at 0x04232078>
>>> f()
10

而如果沒有這種反回函數的功能的話呢

>>> def cal_sum(lst):
    return sum(lst)

>>> f=cal_sum([1,2,3,4])
>>> f
10

便會直接的進行輸出

>>> def calc_sum(lst):
    def lazy_sum():
        return sum(lst)
    return lazy_sum

上面的代碼便是閉包的簡單的函數

內層函數引用了外層函數的變量(參數也算變量),然后返回內層函數的情況,稱為閉包(Closure)

閉包的特點是返回的函數還引用了外層函數的局部變量,所以,要正確使用閉包,就要確保引用的局部變量在函數返回后不能變

>>> def count():
    fs = []
    for i in range(1,4):
        def f():
            return i*i
        fs.append(f)
    return fs

>>> f1,f2,f3 =count()
>>> f1()
9
>>> f2()
9
>>> f3()
9

這里因為i是一個可變的函數所以我們的函數調用三次就會出現變成3

>>> def count():
    fs=[]
    for i in range(1,4):
        def f(m=i):
            return m*m
        fs.append(f)
    return fs

>>> f1,f2,f3=count()
>>> f1()
1
>>> f2()
4
>>> f3()
9

我們只要將變量保存在m里面就好了

高階函數可以接收函數做參數,有些時候,我們不需要顯式地定義函數,直接傳入匿名函數更方便。

>>> list(map(lambda x: x*x,[1,2,3]))
[1, 4, 9]

關鍵字lambda 表示匿名函數,冒號前面的 x 表示函數參數

使用匿名函數,可以不必定義函數名,直接創建一個函數對象,很多時候可以簡化代碼

匿名函數有個限制,就是只能有一個表達式不寫return,返回值就是該表達式的結果。

在有些時候返回函數的時候也可以返回匿名函數。

裝飾器是為了簡化代碼的書寫而發明的一種語法,在這里我們的裝飾器還有一種很簡單的調用方法就是@

>>> def log(f):
    def fn(x):
        print ("call"+f.__name__+'()...')
        return f(x)
    return fn

>>> from functools import reduce
>>> @log
def factorial(n):
    return reduce(lambda x,y:x*y,range(1,n+1))

>>> print (factorial(10))
callfactorial()...
3628800

在這里我們調用的只能是一個只包含有一個參數的函數。因為我們在定義的時候說的就是一個包含只有一個參數的函數。

>>> @log
def add(x,y):
    return x+y

>>> print(add(1,2))
Traceback (most recent call last):
  File "<pyshell#38>", line 1, in <module>
    print(add(1,2))
TypeError: fn() takes 1 positional argument but 2 were given
 
 

  

 

如上所示我們調用的是一個用有兩個參數的add()胡散戶,所以系統會報錯,這里面報錯的理由是fn()擁有的是一個參數,而add()用有的是兩個參數,這里就報錯了。

要讓 @log 自適應任何參數定義的函數,可以利用Python的 *args 和 **kw,保證任意個數的參數總是能正常調用

>>> def log(f):
    def fn(*args,**kw):
        print('call '+f.__name__+"()...")
        return f(*args,**kw)
    return fn

>>> @log
def factorial(n):
    return reduce(lambda x,y:x*y,range(1,n+1))

>>> print (factorial(10))
call factorial()...
3628800
>>> @log
def add(x,y):
    return x+y

>>> print(add(1,2))
call add()...
3

裝飾器也是可以帶參數的。比如:在上面輸出的時候我們需要簡單的表明這些函數是否重要

>>> def log(prefix):#prefix指的是前綴的意思,在這里是隨便的命名
    def log_decorator(f):
        def wrapper(*args,**kw):
            print('[%s]%s()...'%(prefix,f.__name__))
            return f(*args,**kw)
        return wrapper
    return log_decorator

>>> @log('DEBUG')
def test():
    pass

>>> print(test())
[DEBUG]test()...
None

由於我們需要添加一個另外的參數所以我們需要在定義的時候就需要添加一個參數,如果這樣的話我們就像需要多添加一個循環。

這個三層循環運用了閉包的知識,如果哦我們拆開它就會出現錯誤,因為最里面的函數調用了外面的參數,所以才會出現錯誤,如果我們沒有裝飾器的話呢,我們在其他的編程語言下,就需要使用更多的代碼。這也是裝飾器的作用。我們學習Python是喜歡它的簡潔性、易懂性以及它那超過了兩千個庫所帶來的強大的功能。

而裝飾器正是實現這一途徑最好的方法。

>>> def log(prefix):
    def log_decorator(f):
        def wrapper(*args,**kw):
            print('[%s]%s()...'%(prefix,f.__name__))
            return f(*args,**kw)
        return wrapper
    return log_decorator

>>> @log('DEBUG')
def test():
    pass

>>> print(test.__name__)
wrapper

在這里面我們會發現我們的函數的名字已經變化了。這種情況就會導致如果我們編寫一些需要依賴名字的函數的話我們就會出錯。(decorator還改變了函數的__doc__等其它屬性。)

如果要讓調用者看不出一個函數經過了@decorator的“改造”,就需要把原函數的一些屬性復制到新函數中

所以Python內置的functools可以用來自動化完成這個“復制”的任務:

>>> import functools
>>> def log(f):
    @functools.wraps(f)
    def wrapper(*args,**kw):
        print("call...")
        return f(*args,**kw)
    return wrapper

>>> @log
def test():
    pass

>>> print(test.__name__)
test

但是我們還需要注意一點,就是我們的函數里面的那個參數名有的時候會發生改變的,因為我們的參數雖然是進行了復制,但是參數名是可以進行變化的,因此,即使是只有一個參數的簡單函數我們也無法只能簡答的保存函數參數最開賦給的那個參數名,而不是后來傳入的那個參數名。

int()是我們函數自帶的一個轉換函數,我們在使用int()進行轉換的時候我們會發現int()不止有一個參數,它還有一個參數,這個參數是用來指定,函數的轉換進制的。

>>> int ('123')
123
>>> int ('123',8)
83

其實,int這個函數的第二個函數已經被python給默認是10了,這是為了能夠簡便我們的輸入。這種函數我們稱之為偏函數

functools.partial就是幫助我們創建一個偏函數的。

>>> import functools
>>> int2 = functools.partial(int, base=2)
>>> int2('1000000')
64
>>> int2('1010101')
85

functools.partial還可以把參數多的函數變為參數少的函數。

 


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
  © 2014-2022 ITdaan.com