関数型Pythonプログラミングの抂芁

関数型プログラミングに぀いお話すず、倚くの堎合、倚くの「関数型」特性が䞎えられ始めたす。 䞍倉デヌタ、䞀流の関数、および末尟再垰の最適化。 これらは、機胜的なプログラムの䜜成に圹立぀蚀語機胜です。 圌らは、マッピング、カリヌ化、および高階関数の䜿甚に蚀及しおいたす。 これらは、機胜コヌドを蚘述するために䜿甚されるプログラミング手法です。 圌らは、䞊列化、遅延蚈算、および決定論に蚀及しおいたす。 これらは、機胜プログラムの利点です。

ハンマヌむン 機胜コヌドは、副䜜甚がないずいう1぀のプロパティによっお区別されたす。 珟圚の関数倖のデヌタに䟝存せず、関数倖のデヌタを倉曎したせん。 他のすべおの「プロパティ」はこれから掚枬できたす。

機胜しない機胜

a = 0 def increment1(): global a a += 1 


機胜的機胜

 def increment2(a): return a + 1 


リストを調べる代わりに、mapずreduceを䜿甚したす

地図


関数ずデヌタセットを受け入れたす。 新しいコレクションを䜜成し、各デヌタ項目で機胜を実行し、新しいコレクションに戻り倀を远加したす。 新しいコレクションを返したす。

名前のリストを受け入れ、長さのリストを返す単玔なマップ

 name_lengths = map(len, ['', '', '']) print name_lengths # => [4, 4, 3] 


このマップは各芁玠を二乗したす

 squares = map(lambda x: x * x, [0, 1, 2, 3, 4]) print squares # => [0, 1, 4, 9, 16] 


名前付き関数を受け入れたせんが、ラムダを介しお定矩された匿名関数を取りたす。 ラムダパラメヌタヌは、コロンの巊偎に定矩されたす。 関数の本䜓は右偎にありたす。 結果は暗黙的に返されたす。

次の䟋の機胜しないコヌドは、名前のリストを取埗し、それらをランダムなニックネヌムに眮き換えたす。

 import random names = ['', '', ''] code_names = ['', '', ''] for i in range(len(names)): names[i] = random.choice(code_names) print names # => ['', '', ''] 


アルゎリズムは、同じニックネヌムを異なるシヌクレット゚ヌゞェントに割り圓おるこずができたす。 うたくいけば、これは秘密の任務䞭に問題の原因にならないでしょう。

マップを䜿甚しおこれを曞き換えたす。

 import random names = ['', '', ''] secret_names = map(lambda x: random.choice(['', '', '']), names) 


挔習1 。 mapを䜿甚しお次のコヌドを曞き盎しおください。 実名のリストを取埗し、より信頌性の高い方法を䜿甚しおニックネヌムに眮き換えたす。

 names = ['', '', ''] for i in range(len(names)): names[i] = hash(names[i]) print names # => [6306819796133686941, 8135353348168144921, -1228887169324443034] 


私の解決策
 names = ['', '', ''] secret_names = map(hash, names) 



枛らす


Reduceは、関数ずアむテムのセットを受け入れたす。 すべおのアむテムを組み合わせお取埗した倀を返したす。

単玔なreduceの䟋。 セット内のすべおのアむテムの合蚈を返したす。

 sum = reduce(lambda a, x: a + x, [0, 1, 2, 3, 4]) print sum # => 10 


xは珟圚のアむテムであり、バッテリヌです。 これは、ラムダが前の段萜で返す倀です。 reduceは、すべおの倀を反埩凊理し、aずxの珟圚の倀で各ラムダに察しお実行し、次の反埩の結果をaで返したす。

しかし、最初の反埩では䜕が同じですか コレクションの最初の芁玠ず等しく、reduceは2番目の芁玠から始たりたす。 ぀たり、最初のxはセット内の2番目のアむテムず等しくなりたす。

次の䟋では、「captain」ずいう単語が行のリストに衚瀺される頻床を考慮しおいたす。

 sentences = ['  ', '  ', '  , '] cap_count = 0 for sentence in sentences: cap_count += sentence.count('') print cap_count # => 3 


reduceを䜿甚した同じコヌド

 sentences = ['  ', '  ', '  , '] cap_count = reduce(lambda a, x: a + x.count(''), sentences, 0) 


そしお、aの初期倀はどこから来たすか 最初の行の繰り返し数から蚈算するこずはできたせん。 したがっお、reduce関数の3番目の匕数ずしお指定されたす。

なぜmap and reduceが優れおいるのですか


たず、通垞は1行に収たりたす。

第二に、反埩の重芁な郚分であるコレクション、操䜜、および戻り倀は、垞にmap and reduceず同じ堎所にありたす。

第䞉に、ルヌプ内のコヌドは、以前に定矩された倉数の倀を倉曎したり、その埌にあるコヌドに圱響を䞎えたりする可胜性がありたす。 慣䟋により、mapおよびreduceは機胜したす。

第4に、mapずreduceは基本的な操䜜です。 ルヌプを1行ず぀読み取るのではなく、読者がマップを認識し、組み蟌みの耇雑なアルゎリズムを削枛する方が簡単です。

第5に、これらの関数の䟿利でわずかに倉曎された動䜜を蚱可する倚くの友人がいたす。 たずえば、filter、all、any、およびfind。

挔習2 map、reduce、filterを䜿甚しお次のコヌドを曞き換えたす。 フィルタヌは関数ずコレクションを受け入れたす。 関数がTrueを返す察象のコレクションを返したす。

 people = [{'': '', '': 160}, {'  ': '', '  ': 80}, {'name': ''}] height_total = 0 height_count = 0 for person in people: if '' in person: height_total += person['  '] height_count += 1 if height_count > 0: average_height = height_total / height_count print average_height # => 120 


私の解決策
 people = [{'': '', '': 160}, {'  ': '', '  ': 80}, {'name': ''}] heights = map(lambda x: x[''], filter(lambda x: '' in x, people)) if len(heights) > 0: from operator import add average_height = reduce(add, heights) / len(heights) 



呜什ではなく宣蚀的に曞く


次のプログラムは、3台の車のレヌスを゚ミュレヌトしたす。 各時点で、車は前進するかしないかのいずれかです。 プログラムが車で移動したパスを衚瀺するたびに。 5぀の間隔の埌、レヌスは終了したす。

出力䟋

  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 


プログラムテキスト

 from random import random time = 5 car_positions = [1, 1, 1] while time: # decrease time time -= 1 print '' for i in range(len(car_positions)): # move car if random() > 0.3: car_positions[i] += 1 # draw car print '-' * car_positions[i] 


コヌドは必須です。 機胜的なバヌゞョンは宣蚀的です-それはどのように行われるべきかではなく、䜕が行われる必芁があるかを蚘述したす。

関数を䜿甚したす


宣蚀可胜性は、コヌドを関数に挿入するこずで実珟できたす。

 from random import random def move_cars(): for i, _ in enumerate(car_positions): if random() > 0.3: car_positions[i] += 1 def draw_car(car_position): print '-' * car_position def run_step_of_race(): global time time -= 1 move_cars() def draw(): print '' for car_position in car_positions: draw_car(car_position) time = 5 car_positions = [1, 1, 1] while time: run_step_of_race() draw() 


プログラムを理解するために、読者はメむンルヌプを調べたす。 「時間が残っおいる堎合は、レヌスの1ステップを経お結果を衚瀺したす。 もう䞀床時間を確認しおください。」 読者がレヌスステップの仕組みを理解する必芁がある堎合、コヌドを個別に読むこずができたす。

コメントは䞍芁です。コヌド自䜓が説明しおいたす。

コヌドを関数に分割するず、コヌドが読みやすくなりたす。 この手法は関数を䜿甚したすが、ルヌチンずしおのみ䜿甚したす。 圌らはコヌドをパックしたすが、機胜的にしたせん。 関数は、倀を返すのではなく、それらを囲むコヌドに圱響を䞎え、グロヌバル倉数を倉曎したす。 読者が倉数に遭遇した堎合、それがどこから来たのかを芋぀ける必芁がありたす。

このプログラムの機胜バヌゞョンは次のずおりです。

 from random import random def move_cars(car_positions): return map(lambda x: x + 1 if random() > 0.3 else x, car_positions) def output_car(car_position): return '-' * car_position def run_step_of_race(state): return {'time': state['time'] - 1, 'car_positions': move_cars(state['car_positions'])} def draw(state): print '' print '\n'.join(map(output_car, state['car_positions'])) def race(state): draw(state) if state['time']: race(run_step_of_race(state)) race({'time': 5, 'car_positions': [1, 1, 1]}) 


珟圚、コヌドは機胜的な関数に分割されおいたす。 これには3぀の兆候がありたす。 最初の-共有倉数はありたせん。 timeずcar_positionsはraceに盎接枡されたす。 2番目は、関数がパラメヌタヌを取るこずです。 第䞉に、倉数は関数内で倉化せず、すべおの倀が返されたす。 run_step_of_raceが次のステップを実行するたびに、次のステップに再び枡されたす。

以䞋に2぀の関数zeroずoneを瀺したす。

 def zero(s): if s[0] == "0": return s[1:] def one(s): if s[0] == "1": return s[1:] 


れロは文字列sを取りたす。 最初の文字が0の堎合、文字列の残りを返したす。 そうでない堎合は、なし。 最初の文字が1の堎合、oneは同じこずを行いたす。

rule_sequence関数を想像しおください。 文字列ず、0個ず1個の関数で構成されるルヌル関数のリストを受け取りたす。 最初のルヌルを呌び出しお、文字列を枡したす。 Noneが返されない堎合、返された倀を受け取り、次のルヌルを呌び出したす。 などなど。 Noneが返された堎合、rule_sequenceは停止し、Noneを返したす。 それ以倖の堎合、最埌のルヌルの意味。

入力および出力デヌタの䟋

 print rule_sequence('0101', [zero, one, zero]) # => 1 print rule_sequence('0101', [zero, zero]) # => None 


rule_sequenceの呜什型バヌゞョン

 def rule_sequence(s, rules): for rule in rules: s = rule(s) if s == None: break return s 


挔習3 。 このコヌドはルヌプを䜿甚したす。 再垰を䜿甚しお宣蚀的に曞き換えたす。

私の解決策
 def rule_sequence(s, rules): if s == None or not rules: return s else: return rule_sequence(rules[0](s), rules[1:]) 



パむプラむンを䜿甚する


次に、パむプラむンず呌ばれる手法を䜿甚しお、他の皮類のサむクルを曞き換えたす。

次のサむクルでは、名前、間違った原産囜、いく぀かのグルヌプのステヌタスを含む蟞曞を倉曎したす。

 bands = [{'name': 'sunset rubdown', 'country': 'UK', 'active': False}, {'name': 'women', 'country': 'Germany', 'active': False}, {'name': 'a silver mt. zion', 'country': 'Spain', 'active': True}] def format_bands(bands): for band in bands: band['country'] = 'Canada' band['name'] = band['name'].replace('.', '') band['name'] = band['name'].title() format_bands(bands) print bands # => [{'name': 'Sunset Rubdown', 'active': False, 'country': 'Canada'}, # {'name': 'Women', 'active': False, 'country': 'Canada' }, # {'name': 'A Silver Mt Zion', 'active': True, 'country': 'Canada'}] 


関数「フォヌマット」の名前は䞀般的すぎたす。 䞀般に、コヌドはいく぀かの懞念を匕き起こしたす。 1぀のサむクルで3぀の異なるこずが起こりたす。 「囜」キヌの倀が「カナダ」に倉わりたす。 ドットが削陀され、名前の最初の文字が倧文字に倉曎されたす。 コヌドが䜕をすべきかを理解するこずは困難であり、そうするかどうかを蚀うこずは困難です。 䜿甚、テスト、および䞊列化は困難です。

比范する

 print pipeline_each(bands, [set_canada_as_country, strip_punctuation_from_name, capitalize_names]) 


すべおがシンプルです。 ヘルパヌ関数は互いに連鎖しおいるため、機胜しおいるように芋えたす。 前を終了-次を入力したす。 怜蚌、再利甚、怜蚌、䞊列化が簡単です。

pipeline_eachは䞀床に1぀ず぀グルヌプを反埩凊理し、set_canada_as_countryなどの倉換関数に枡したす。 関数をすべおのグルヌプに適甚した埌、pipeline_eachはそれらからリストを䜜成し、次のリストに枡したす。

倉換関数を芋おみたしょう。

 def assoc(_d, key, value): from copy import deepcopy d = deepcopy(_d) d[key] = value return d def set_canada_as_country(band): return assoc(band, 'country', "Canada") def strip_punctuation_from_name(band): return assoc(band, 'name', band['name'].replace('.', '')) def capitalize_names(band): return assoc(band, 'name', band['name'].title()) 


それぞれがグルヌプキヌを新しい倀に関連付けたす。 元のデヌタを倉曎せずにこれを行うのは難しいため、assocで解決したす。 deepcopyを䜿甚しお、枡された蟞曞のコピヌを䜜成したす。 各関数はコピヌを倉換し、そのコピヌを返したす。

すべおが正垞なようです。 倉曎から保護された元のデヌタ。 ただし、コヌド内でデヌタを倉曎できる堎所は2぀ありたす。 strip_punctuation_from_nameで、ドットなしの名前は、元の名前でreplaceを呌び出すこずで䜜成されたす。 capitalize_namesでは、タむトルず元の名前に基づいお、最初の倧文字で名前が䜜成されたす。 replaceずtimeが機胜しない堎合、strip_punctuation_from_namewith capitalize_namesは機胜したせん。

幞いなこずに、それらは機胜的です。 Pythonでは、文字列は䞍倉です。 これらの関数は、文字列のコピヌで機胜したす。 うヌん、神に感謝したす。

Pythonの文字列ず蟞曞その可倉性のこの察照は、Clojureのような蚀語の利点を瀺しおいたす。 そこで、プログラマはデヌタを倉曎するかどうかを考える必芁はありたせん。 倉わりたせん。

挔習4 。 pipeline_each関数を䜜成しおみおください。 操䜜のシヌケンスに぀いお考えおください。 グルヌプ-配列では、最初の倉換関数のために䞀床に1぀ず぀枡されたす。 次に、結果の配列には、2番目の関数に察しお1぀の小さなものが枡されたす。

私の解決策
 def pipeline_each(data, fns): return reduce(lambda a, x: map(x, a), fns, data) 



結果ずしお、3぀の倉換関数はすべお、グルヌプの特定のフィヌルドを倉曎したす。 callを䜿甚しお、このための抜象化を䜜成できたす。 関数ず、それが適甚されるキヌを受け入れたす。

 set_canada_as_country = call(lambda x: 'Canada', 'country') strip_punctuation_from_name = call(lambda x: x.replace('.', ''), 'name') capitalize_names = call(str.title, 'name') print pipeline_each(bands, [set_canada_as_country, strip_punctuation_from_name, capitalize_names]) 


たたは、読みやすさを犠牲にしたす。

 print pipeline_each(bands, [call(lambda x: 'Canada', 'country'), call(lambda x: x.replace('.', ''), 'name'), call(str.title, 'name')]) 


呌び出しのコヌド

 def assoc(_d, key, value): from copy import deepcopy d = deepcopy(_d) d[key] = value return d def call(fn, key): def apply_fn(record): return assoc(record, key, fn(record.get(key))) return apply_fn 


ここで䜕が起こっおいたすか

䞀。 呌び出しは高階関数です 別の関数を匕数ずしお受け取り、関数を返したす。

二。 apply_fnは倉換関数に䌌おいたす。 ゚ントリグルヌプを取埗したす。 レコヌド[キヌ]の倀を怜玢したす。 fnを呌び出したす。 結果をレコヌドのコピヌに割り圓おお返したす。

䞉。 呌び出し自䜓は䜕もしたせん。 すべおの䜜業はapply_fnによっお行われたす。 pipeline_eachの䟋では、apply_fnの1぀のむンスタンスが「country」を「Canada」に蚭定したす。 別の-最初の文字を倧文字にしたす。

4。 apply_fnむンスタンスが実行されるず、fnおよびキヌ関数はスコヌプ内で䜿甚できなくなりたす。 これらはapply_fn匕数たたはロヌカル倉数ではありたせん。 しかし、それらぞのアクセスは可胜になりたす。 関数を定矩するずき、閉じる倉数ぞの参照を保存したす—倉数は関数の倖郚で定矩され、内郚で䜿甚されたす。 関数が開始されるず、倉数はロヌカル間で怜玢され、次に匕数間で怜玢され、次に閉じたものぞの参照間で怜玢されたす。 fnずキヌがありたす。

5。 通話䞭のグルヌプに関する蚀及はありたせん。 これは、呌び出しがパむプラむンの内容に関係なく、パむプラむンの䜜成に䜿甚できるためです。 特に、関数型プログラミングは、䜜曲ず再利甚に適した䞀般的な関数のラむブラリを構築したす。

よくできたした。 クロヌゞャ、高階関数、およびスコヌプはすべおいく぀かの段萜にありたす。 クッキヌず䞀緒にお茶を飲むこずもできたす。

これらのグルヌプの凊理がもう1぀残っおいたす。 名前ず囜を陀くすべおを削陀したす。 Extract_name_and_country関数

 def extract_name_and_country(band): plucked_band = {} plucked_band['name'] = band['name'] plucked_band['country'] = band['country'] return plucked_band print pipeline_each(bands, [call(lambda x: 'Canada', 'country'), call(lambda x: x.replace('.', ''), 'name'), call(str.title, 'name'), extract_name_and_country]) # => [{'name': 'Sunset Rubdown', 'country': 'Canada'}, # {'name': 'Women', 'country': 'Canada'}, # {'name': 'A Silver Mt Zion', 'country': 'Canada'}] 


extract_name_and_countryは、pluckず呌ばれる䞀般化された圢匏で蚘述できたす。 次のように䜿甚されたす。

 print pipeline_each(bands, [call(lambda x: 'Canada', 'country'), call(lambda x: x.replace('.', ''), 'name'), call(str.title, 'name'), pluck(['name', 'country'])]) 


挔習5 。 pluckは、レコヌドから抜出するキヌのリストを受け入れたす。 曞いおみおください。 これは高次関数です。

私の解決策
 def pluck(keys): def pluck_fn(record): return reduce(lambda a, x: assoc(a, x, record[x]), keys, {}) return pluck_fn 



そしお今䜕

機胜的なコヌドは埓来のコヌドずよく合いたす。 この蚘事からの倉換は、どの蚀語でも䜿甚できたす。 コヌドを詊しおみおください。

マヌシャ、ペティア、ノァシャに぀いお芚えおおいおください。 リストの反埩をマップに倉換しお削枛したす。

レヌスを芚えおください。 コヌドを関数に分割し、それらを機胜的にしたす。 ルヌプを再垰に倉えたす。

グルヌプを芚えおおいおください。 䞀連の操䜜をパむプラむンに倉換したす。

Source: https://habr.com/ru/post/J257903/


All Articles