Python学習ノート2:関数式プログラミング


前回のブログでは、Pythonのインストール環境、変数とデータ型(Python内蔵の基本タイプ);ListとTuple(シーケンスの集合タイプ);条件判断とサイクル(制御プログラムフロー);DictとSet(keyによってアクセスされる集合タイプ);関数(定義および呼び出し関数);スライス(listをスライスする方法);反復(forループ反復セットタイプを使用する方法);リスト生成式(リストを迅速に生成する方法).このブログでは、関数式プログラミングを学びます.
1.関連概念
関数:function関数式:functional,プログラミングパターン関数式プログラミングの特徴:(1)計算を命令ではなく関数と見なす(2)純関数式プログラミング:変数を必要とせず、副作用がなく、テストが簡単(3)高次関数をサポートするコード簡潔Pythonがサポートする関数式プログラミング:(1)純関数式プログラミングではない:変数があることを許可する(2)高次関数をサポートする:関数は変数として伝達することもできる(3)閉包をサポートする:閉包があれば関数に戻る(4)匿名関数を限度的にサポートする
2.高次関数高次関数とは、関数をパラメータとして受信できる関数のこと.変数は関数を指すことができ、関数のパラメータは変数を受信することができるので、ある関数は別の関数をパラメータとして受信することができ、関数をパラメータとして受信できる関数は高次関数である.
(1)map()関数map()はPythonに内蔵された高次関数であり,1つの関数fと1つのlistを受信し,関数fをlistの各要素に順次作用させることで新しいlistを得て返す.
def format_name(s):
    return s[0].upper()+s[1:].lower()

print map(format_name, ['adam', 'LISA', 'barT'])

出力結果:[‘Adam’,‘Lisa’,‘Bart’]
(2)reduce()関数reduce()関数もPythonに内蔵された高次関数である.reduce()関数が受信するパラメータはmap()と類似しており、1つの関数f、1つのlistであるが、動作はmap()とは異なり、reduce()が入力する関数fは2つのパラメータを受信しなければならず、reduce()はlistの各要素に対して関数fを繰り返し呼び出し、最終結果値を返す.例えば、f関数を記述し、xとyを受信し、xとyの和を返します.
def f(x, y):
    return x + y

reduce(f,[1,3,5,7,9])を呼び出すと、reduce関数は次のように計算されます.
f(1, 3),   4;
      3     :f(4, 5),   9;
      4     :f(9, 7),   16;
      5     :f(16, 9),   25;
          ,    ,    25。

上記の計算は実際にlistのすべての要素を合計します.Pythonには求和関数sum()が内蔵されているが,reduce()を用いて求和するのも簡単である.reduce()は、計算の初期値として3番目のオプションパラメータを受信することもできる.初期値を100に設定すると、次のように計算されます.
reduce(f, [1, 3, 5, 7, 9], 100)

結果は125になります.第1ラウンドの計算は、初期値と第1要素:f(100,1)を計算し、結果は101になるからです.
(3)filter()関数filter()関数は1つの関数fと1つのlistを受信し,この関数fの役割は各要素を判断し,TrueまたはFalseを返し,filter()は判断結果に基づいて条件に合わない要素を自動的にフィルタリングし,条件に合致する要素からなる新しいlistを返すことである.filter()を使用すると、Noneや空の文字列を削除するなど、多くの有用な機能を実行できます.
def is_not_empty(s):
    return s and len(s.strip()) > 0
filter(is_not_empty, ['test', None, '', 'str', '  ', 'END'])

結果:[‘test’,‘str’,‘END’]注意:s.strip(rm)s文字列の先頭、末尾のrmシーケンスの文字を削除します.rmが空の場合、デフォルトでは空白文字(''','r','t','')が削除されます.
(4)sorted()関数Pythonに内蔵されたsorted()関数はlistをソートすることができるが、sorted()も高次関数であり、カスタムソートを実現するために比較関数を受信することができ、比較関数の定義は、比較する2つの要素x,y、例えば、xがyの前に並ぶべきであり、-1を返し、xがyの後ろに並ぶべきであれば、1を返す.xとyが等しい場合は、0を返します.
def cmp_ignore_case(s1, s2):
    t1=s1.lower()
    t2=s2.lower()
    if t1 > t2:
        return 1
    if t1 < t2:
        return -1
    return 0

print sorted(['bob', 'about', 'Zoo', 'Credit'], cmp_ignore_case)

出力:[‘about’,‘bob’,‘Credit’,‘Zoo’]
(5)戻り関数Pythonの関数はint,str,list,dictなどのデータ型だけでなく,関数も返すことができる.たとえば、関数f()を定義し、関数gを返します.
def f():
    print 'call f()...'
    #     g:
    def g():
        print 'call g()...'
    #     g:
    return g

上記の関数定義をよく観察すると,関数f内部にもう一つの関数gを定義した.関数gもオブジェクトであり、関数名gは関数gを指す変数であるため、最外層関数fは変数g、すなわち関数g自体を返すことができる.関数fを呼び出すと、fが返す関数が得られます.
>>> x = f()   #   f()
call f()...
>>> x   #   x f()     :
<function g at 0x1037bf320>
>>> x()   # x    ,      
call g()...   #   x()    g()       

(6)閉包という内層関数は外層関数の変数(パラメータも変数)を参照して内層関数を返す場合を閉包(Closure)と呼ぶ.閉パケットの特徴は、返される関数が外層関数のローカル変数も参照することです.したがって、閉パケットを正しく使用するには、参照されるローカル変数が関数が返された後に変化しないことを確認する必要があります.
#       3   ,    1x1,2x2,3x3:
def count():
    fs = []
    for i in range(1, 4):
        def f():
             return i*i
        fs.append(f)
    return fs

f1, f2, f3 = count()
print f1(), f2(), f3()
#   :9,9,9

問題の原因は、関数が呼び出し時にのみ外層パラメータを取得するため、関数が定義時にiの値を取得できれば、問題を解決できるからです.デフォルトのパラメータは、定義時にi値を取得し、実行時にパラメータ入力を必要としない機能を完了します.
def count():
    fs = []#       
    for i in range(1, 4):
        def f(m=i):#      ,      m     
            return m * m
        fs.append(f)
    return fs

f1, f2, f3 = count()
print f1(), f2(), f3()
#   :1,4,9

(7)匿名関数Pythonでは匿名関数に限られたサポートを提供している.また、map()関数を例に、f(x)=x 2を計算する場合、f(x)の関数を定義する以外に、匿名関数を直接入力することもできます.
>>> map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9])
[1, 4, 9, 16, 25, 36, 49, 64, 81]

比較から,匿名関数lambda x:x*xは実際には:
def f(x):
    return x * x

キーワードlambdaは匿名関数を表し、コロンの前のxは関数パラメータを表す.匿名関数には、returnを書かずに式が1つしかないことに制限があります.戻り値は式の結果です.
(8)decorator装飾器の問題:実行時に機能を動的に増やしたいが、関数自体のコードを変更したくない関数を定義した.次のようになります.
def f1(x):
    return x*2
def f2(x):
    return x*x
def f3(x):
    return x*x*x

方法1:元の関数の定義を直接変更します.
def f1(x):
    print "call f1(x)..."
    return x*2
def f2(x):
    print "call f2(x)..."
    return x*x
def f3(x):
    print "call f3(x)..."
    return x*x*x

この方法の欠点は,元の関数の定義を修正する必要があり,できない機能を追加する必要がある場合には元の関数の定義を複数回修正する必要があり,複数の関数に同じ機能を加える場合には,この方法には多くの重複コードが必要である.
方法2:装飾器decoraterは、高次関数を使用して、パラメータとして関数を受信し、包装し、新しい関数を返します.
# -*- coding:UTF-8 -*-

#     
def new_fn(f):
    def fn(x):
        print "call"+f.__name__+"()..."
        return f(x)
    return fn

@new_fn
def f1(x):
    return x*2
@new_fn
def f2(x):
    return x*x
@new_fn
def f3(x):
    return x*x*x


print f1(5)
print f2(2)
print f3(3)

'''
g1=new_fn(f1)
print g1(5)

f1=new_fn(f1)
print f1(5)
'''

装飾器の役割:簡略コードを極めて大きくすることができ、各関数が重複コードを記述することを避けることができます.印刷ログ:@log検出パフォーマンス:@performanceデータベーストランザクション:@transaction URLルーティング:@post('/register')
# -*- coding:UTF-8 -*-
import time

#     
def new_fn(f):
    def fn(x):
        print "call "+f.__name__+"()..."
        return f(x)
    return fn

@new_fn
def f1(x):
    return x*2
@new_fn
def f2(x):
    return x*x
@new_fn
def f3(x):
    return x*x*x


print f1(5)
print f2(2)
print f3(3)

'''
g1=new_fn(f1)
print g1(5)

f1=new_fn(f1)
print f1(5)
'''

#               
#print "----------------     ----------------".decode('utf-8').encode('gbk')
print "----------------     ----------------"

#    
'''
               
def log(f):
    def fn(x):
        print "call "+f.__name__+"()..."
        return f(x)
    return fn
'''
#   @log             ,    Python  *args   **kw,                
def log(f):
    def fn(*args,**kw):
        print "call "+f.__name__+"()..."
        return f(*args,**kw)
    return fn

#    
def performance(f):
    def fn(*args,**kw):
        startTime=time.clock()
        res=f(*args,**kw)
        endTime=time.clock()
        print "running time is %f s"%(endTime-startTime)
        return res
    return fn

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

print factorial(20)

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

print add(1,2)
     :
call f1()...
10
call f2()...
4
call f3()...
27
----------------     ----------------
call factorial()...
running time is 0.000052 s
2432902008176640000
call add()...
running time is 0.000022 s
3

(9)偏関数functools.partialは1つのパラメータの多い関数を1つのパラメータの少ない新しい関数に変えることができて、少ないパラメータは作成時にデフォルト値を指定する必要があって、このようにして、新しい関数の呼び出しの難易度は下げました.
import functools

#sorted(iterable[, cmp[, key[, reverse]]])

def cmp_ignore_case(s1,s2):
    t1=s1.lower()
    t2=s2.lower()
    if t1 > t2:
        return 1
    if t1 < t2:
        return -1
    return 0

sorted_ignore_case = functools.partial(sorted,cmp=cmp_ignore_case)
sorted_ignore_case2 = functools.partial(sorted,cmp=lambda x,y:cmp(x.lower(), y.lower()))
sorted_reverse = functools.partial(sorted,reverse=True)

print sorted(['bob', 'about', 'Zoo', 'Credit'])
print sorted_ignore_case(['bob', 'about', 'Zoo', 'Credit'])
print sorted_ignore_case2(['bob', 'about', 'Zoo', 'Credit'])
print sorted_reverse(['bob', 'about', 'Zoo', 'Credit'])

出力結果:
['about', 'bob', 'Credit', 'Zoo']
['about', 'bob', 'Credit', 'Zoo']
['bob', 'about', 'Zoo', 'Credit']