ホームページ  >  記事  >  バックエンド開発  >  Python 上級: 高階関数の詳細な説明

Python 上級: 高階関数の詳細な説明

高洛峰
高洛峰オリジナル
2017-03-09 11:03:121531ブラウズ

この記事では Python の上級について説明します。高階関数の詳細な説明が必要な友人は、

関数プログラミング

関数を参照してください。関数は、Python の組み込みサポートによってサポートされるカプセル化の一種です。大きなコードを関数に分割します。 , レイヤーごとの関数呼び出しを通じて、複雑なタスクを単純なタスクに分解できます。この分解はプロセス指向プログラミングと呼ばれます。関数はプロセス指向プログラミングの基本単位です。

関数型プログラミング (「式」という追加単語に注意してください) - 関数型プログラミングは、プロセス指向プログラミングに起因することもありますが、その考え方は数学的計算に近いものです。

私たちはまずコンピューターとコンピューティングの概念を理解する必要があります。

コンピュータレベルでは、CPUが足し算、引き算、掛け算、割り算などの命令コードを実行したり、各種の条件判定やジャンプ命令を実行したりするため、アセンブリ言語はコンピュータに最も近い言語です。

計算とは、数学的な意味での計算を指します。計算が抽象的であればあるほど、コンピューターのハードウェアから遠ざかります。

プログラミング言語に対応します。つまり、低レベル言語はコンピューターに近く、抽象度が低く、実行効率が高くなります。たとえば、C 言語はコンピューティングに近く、高レベル言語はコンピューティングに近くなります。 Lisp 言語など、抽象化レベルが高く、実行効率が低くなります。

関数型プログラミングは、高度な抽象度を備えたプログラミング パラダイムです。純粋な関数型プログラミング言語で記述された関数には変数がありません。したがって、どのような関数でも、入力が確実である限り、出力はこれを使用します。一種の純粋関数であり、副作用はありません。変数を使用できるプログラミング言語では、関数内の変数の状態が不定であるため、同じ入力でも異なる出力が得られる可能性があるため、この種の関数には副作用があります。

関数型プログラミングの特徴の 1 つは、関数自体をパラメータとして別の関数に渡すことができ、関数を返すこともできることです。

Python は関数型プログラミングの部分的なサポートを提供します。 Python では変数の使用が許可されているため、Python は純粋な関数型プログラミング言語ではありません。

高階関数

高階関数は英語でHigher-order functionと言います。高階関数とは何ですか?実際のコードを例として使用して、概念を段階的に深めます。

変数は関数を指すことができます

Python の組み込み絶対値関数 abs() を例として挙げます。この関数を呼び出すには、次のコードを使用します。

>>>
しかし、腹筋だけを書くのはどうでしょうか?

>>> abs



abs(-10) が関数呼び出しであり、abs が関数そのものであることがわかります。

関数呼び出しの結果を取得するには、結果を変数に代入します:

>>> x = abs(-10)>>>

>>> f = abs
>>> f

結論: 関数自体を変数に割り当てることもできます。関数を指します。

変数が関数を指している場合、その関数は変数を通じて呼び出すことができますか?次のコードで確認します:

>>> f = abs>>> f(-10)10

成功しました。これは、変数 f が abs 関数自体を指すことを意味します。 abs() 関数を直接呼び出すことは、変数 f() を呼び出すこととまったく同じです。

関数名も変数です

では、関数名は何でしょうか?関数名は実際には関数を指す変数です。関数 abs() の場合、関数名 abs は絶対値を計算できる関数を指す変数とみなすことができます。

abs が他のオブジェクトを指している場合はどうなりますか?

>>> abs = 10

>>> abs(-10)

トレースバック (最新の最後の呼び出し):

ファイル ""、 内TypeError: 'int' オブジェクトは呼び出し可能ではありません

abs を 10 に指定した後は、abs(-10) を介して関数を呼び出すことはできません。 abs 変数は絶対値関数を指さず、整数 10 を指しているからです。

もちろん、実際のコードはこのように記述してはいけません。これは、関数名も変数であることを示すためです。 abs 機能を復元するには、Python インタラクティブ環境を再起動します。

注: abs 関数は実際には importbuiltins モジュールで定義されているため、abs 変数のポインティングを変更して他のモジュールで有効にするには、 importbuiltins.abs = 10 を使用します。

関数の受け渡し

変数は関数を指し、関数のパラメータは変数を受け取ることができるため、関数は別の関数をパラメータとして受け取ることができます。この種の関数は高階関数と呼ばれます。

最も単純な高階関数の 1 つ:

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

add(-5, 6, abs) を呼び出すと、関数の定義によれば、パラメータ x、y、f はそれぞれ -5、6、abs を受け取ります。

x = -5
y = 6
f = abs
f(x) + f と推定できます。 (y) = => abs(-5) + abs(6) ==> 11return 11

>>> add(-5, 6, abs)11

Write high オーダー関数は、関数のパラメーターが他の関数を受け取ることを可能にするものです。

概要

関数をパラメーターとして渡すこのような関数は、高階関数と呼ばれます。この高度に抽象的なプログラミング パラダイムを指します。

map/reduce

Pythonには組み込みのmap()関数とreduce()関数があります。

Googleの有名な論文「MapReduce: Simplified Data Processing on Large Clusters」を読んでいれば、map/reduceの概念は大体理解できると思います。

まず地図を見てみましょう。 map() 関数は 2 つのパラメーターを受け取り、1 つは関数で、もう 1 つはマップであり、渡された関数をシーケンスの各要素に順番に適用し、結果を新しいイテレーターとして返します。

たとえば、関数 f(x)=x2 があり、この関数をリスト [1, 2, 3, 4, 5, 6, 7, 8, 9] に適用したい場合は、次のように使用できます。 map() は次のように実装されます:

Python 上級: 高階関数の詳細な説明

次に、Python コードで実装します:

>>> def f(x):... return x * x
...>> ;> r = マップ(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])>>> リスト(r)
[1, 4, 9, 16, 25] , 36, 49 , 64, 81]

map() 渡される最初のパラメーターは f で、これは関数オブジェクト自体です。結果 r は Iterator であり、Iterator は遅延シーケンスであるため、 list() 関数を使用してシーケンス全体を計算し、リストを返します。

map() 関数は必要ないと思うかもしれませんが、ループを作成して結果を計算できます:

L = []for n in [1, 2, 3, 4, 5, 6, 7 , 8, 9]:
L.append(f(n))
print(L)

も確かに可能ですが、上記のループコードを見ると、「それぞれに f(x) を適用する」ことが一目でわかります。リストの要素を選択し、結果を新しいリストに生成します?

つまり、map() は高階関数として実際に演算規則を抽象化するので、単純な f(x)=x2 を計算するだけでなく、あらゆる複雑な関数、たとえばすべての関数を計算することもできます。このリストの数値を文字列に変換します:

>>> list(map(str, [1, 2, 3, 4, 5, 6, 7, 8, 9]))['1', '2 '、'3'、'4'、'5'、'6'、'7'、'8'、'9']

必要なコードは 1 行だけです。

reduce の使い方を見てみましょう。 reduce は関数をシーケンス [x1, x2, x3, ...] に適用します。この関数は 2 つのパラメーターを受け取る必要があります。reduce はシーケンスの次の要素で結果の累積計算を続行します。

reduce ( f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)

たとえば、シーケンスを合計するには、reduce を使用して実装できます:

> ;>> functools から import reduce>>> def add(x, y):... return x + y
...>>> , 7, 9])25

もちろん、合計演算は Python の組み込み関数 sum() を使用して直接実行でき、reduce を使用する必要はありません。

ただし、シーケンス [1, 3, 5, 7, 9] を整数 13579 に変換する場合は、reduce が便利です。 fn( x, y):... return x * 10 + y

...>>>reduce(fn, [1, 3, 5, 7, 9])13579



この例自体はあまり役に立ちませんが、文字列 str もシーケンスであると考えると、上記の例を少し変更して、map() を使用することで、str を int に変換する関数を作成できます。 functoolsインポートreduce>>> def fn(x, y):... return x * 10 + y

...>>> return {'0': 0、'1': 1、'2': 2、'3': 3、'4': 4、'5': 5、'6': 6、'7': 7、'8': 8、 '9 ': 9}[s]

...>>>reduce(fn,map(char2num, '13579'))13579


str2int にまとめられた関数は次のとおりです:

functools から importducedef str2int(s): def fn(x, y): return x * 10 + y def char2num(s): return {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}[s] returnreduce(fn, map(char2num, s))

は、ラムダ関数を使用してさらに簡素化することもできます:

from functools import Reducef char2num(s): return {'0': 0, '1': 1, '2': 2 、'3': 3、'4': 4、'5': 5、'6': 6、'7': 7、'8': 8、'9': 9}[s]def str2int(s ) : returnreduce(lambda x, y: x * 10 + y, map(char2num, s))

言い換えると、Python が int() 関数を提供しないと仮定すると、文字列を変換するために自分で関数を作成できます。整数関数に変換するだけで、必要なコードは数行だけです。

lambda関数の使い方は後ほど紹介します。

filter

Python の組み込みの filter() 関数は、シーケンスをフィルター処理するために使用されます。

map() と同様に、filter() も関数とシーケンスを受け取ります。 map() とは異なり、filter() は渡された関数を各要素に順番に適用し、戻り値が True か False かに基づいて要素を保持するか破棄するかを決定します。

たとえば、リストで偶数を削除し、奇数のみを保持するには、次のように記述できます:

def is_odd(n): return n % 2 == 1

list(filter(is_odd, [1, 2) , 4 , 5, 6, 9, 10, 15]))# 結果: [1, 5, 9, 15]

シーケンス内の空の文字列を削除します。次のように記述できます:

def not_empty(s) : s と s.strip() を返します

list(filter(not_empty, ['A', '', 'B', None, 'C', ' ']))# 結果: ['A', 'B ' , 'C']

高階関数 filter() を使用するための鍵は、「フィルタリング」関数を正しく実装することであることがわかります。

filter() 関数は遅延シーケンスである Iterator を返すので、filter() に計算結果を強制的に完了させるには、list() 関数を使用してすべての結果を取得し、リストを返す必要があることに注意してください。

sorted

ソートアルゴリズム

ソートもプログラムでよく使われるアルゴリズムです。バブル ソートとクイック ソートのどちらを使用する場合でも、ソートの中心となるのは 2 つの要素のサイズを比較することです。それが数値の場合は直接比較できますが、それが文字列または 2 つの辞書の場合はどうなるでしょうか?数学的大きさを直接比較することは無意味であるため、比較プロセスは関数を通じて抽象化する必要があります。

Python の組み込みのsorted()関数はリストを並べ替えることができます:

>sorted([36, 5, -12, 9, -21])[-21, -12, 5, 9 , 36]

さらに、sorted() 関数は、絶対値による並べ替えなどのカスタマイズされた並べ替えを実装するためのキー関数も受け取ることができます。 [ 36, 5, -12, 9, -21], key=abs)

[5, 9, -12, -21, 36]



key で指定された関数はリストの各要素に作用し、に基づいて key 関数によって返された結果は並べ替えられます。元のリストと key=abs で処理されたリストを比較します:

list = [36, 5, -12, 9, -21]keys = [36, 5, 12, 9, 21]


thensorted()関数はキーでソートし、対応する関係に従ってリストの対応する要素を返します:

keys sort result => [5, 9, 12, 21, 36]

, 9, -12, -21, 36]



文字列ソートの別の例を見てみましょう:

>>>sorted(['bob', 'about', 'Zoo', 'Credit' ])['Credit', 'Zoo', 'about' , 'bob']

デフォルトでは、文字列の並べ替えは ASCII サイズ比較に基づいて行われるため、結果として大文字の Z が小文字の a よりも前にランクされます。

ここで、並べ替えでは大文字と小文字を無視してアルファベット順に並べ替えることを提案します。このアルゴリズムを実装するために、キー関数を使用して文字列を大文字と小文字を無視した並べ替えにマップできる限り、既存のコードに大きな変更を加える必要はありません。大文字と小文字を無視して 2 つの文字列を比較するということは、実際には、まず文字列を大文字 (または小文字) に変更してから比較することを意味します。

このようにして、大文字と小文字を無視したソートを実現するために key 関数をsortedに渡します:

>>>sorted(['bob', 'about', 'Zoo', 'Credit'], key =str . lower)

['about', 'bob', 'Credit', 'Zoo']


逆ソートを実行するには、キー関数を変更する必要はありません。3 番目のパラメーター reverse=True を渡すことができます。

>>>sorted(['bob', 'about', 'Zoo', 'Credit'], key=str. lower, reverse=True)

['Zoo', 'Credit', 'bob ', 'about']


上記の例からわかるように、高階関数の抽象化能力は非常に強力であり、コア コードは非常にシンプルに保つことができます。

概要

sorted() も高階関数です。 sorted() でソートするための鍵は、マッピング関数を実装することです。

戻り関数

戻り値としての関数

関数をパラメータとして受け入れることに加えて、高階関数は結果値として関数を返すこともできます。

変数パラメータの合計を実装してみましょう。通常、合計関数は次のように定義されます:

def calc_sum(*args):
ax = 0 for n in args:
ax = ax + n return ax

ただし、すぐに合計する必要がない場合は、 , ですが、以下のコードで、必要に応じて再計算する場合はどうすればよいでしょうか?合計結果を返す代わりに、合計関数

def Lazy_sum(*args): def sum():
ax = 0 for n in args:
ax = ax + n

lazy_sum を呼び出すとき() の場合、返されるのは合計結果ではなく、合計関数です。 function Lazy_sum..sum at 0x101c6ed90>

合計の結果は、関数 f が呼び出されたときに実際に計算されます:

>>> この例では、関数 raise で関数 sum を定義します。 、内部関数 sum は、外部関数 lagy_sum のパラメータとローカル変数を参照できます。lazy_sum が関数 sum を返すと、関連するパラメータと変数が返された関数に保存されます。この「クロージャ」と呼ばれるプログラム構造は大きな力を持っています。 。

もう 1 つ注意してください。lazy_sum() を呼び出すと、同じパラメータが渡された場合でも、各呼び出しは新しい関数を返します。 7, 9)>>> f2 = Lazy_sum(1, 3, 5, 7, 9)>>> f1==f2False

f1() と f2() の呼び出し結果は互いに影響を及ぼします。

クロージャ

返された関数はその定義内のローカル変数 args を参照するので、関数が関数を返すとき、その内部ローカル変数も新しい関数によって参照されることに注意してください。実装するのは簡単ではありません。

注意が必要なもう 1 つの問題は、返された関数がすぐには実行されず、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 になります。

クロージャを返すときに留意すべき点の 1 つは、return 関数はループ変数や、後で変更される変数を参照してはいけないということです。

ループ変数を参照する必要がある場合はどうすればよいですか?この方法では、別の関数を作成し、関数のパラメーターを使用してループ変数の現在の値をバインドします。ループ変数がその後どのように変化しても、関数パラメーターにバインドされた値は変更されません。

def count(): def f(j ): def g(): ' を使用して ' を使用して ' を使用して ' を使用s ' を通じて ‐ ‐ ‐ ‐ ‐ fs.append(f(i)) # ‐ fs. append(f(i)) # f(i) はすぐに実行されるため、i 現在の値が f() に渡されます return fs


結果を見てください:

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

欠点は、コードが長いことです。コードを短くするために使用されます。

概要

関数は計算結果または関数を返すことができます。

関数を返すときは、その関数がまだ実行されていないことに注意し、返された関数内で変更される可能性のある変数を参照しないでください。

匿名関数


関数を渡すとき、関数を明示的に定義する必要がない場合があります。匿名関数を直接渡す方が便利です。

Python では、匿名関数のサポートが制限されています。さらに map() 関数を例に挙げると、f(x)=x2 を計算するときに、f(x) の関数を定義することに加えて、匿名関数を直接渡すこともできます。 list(map(ラムダ x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9]))[1, 4, 9, 16, 25, 36, 49, 64, 81 ]

pass 比較から、匿名関数 lambda x: x * x は実際には次のとおりであることがわかります:

def f(x): return x * x

キーワード lambda は匿名関数を表し、コロンの前の x は関数パラメータを表します。

匿名関数には制限があり、式は 1 つしか持てません。戻り値は式の結果です。

匿名関数を使用することには、関数に名前がないため、関数名の競合を心配する必要がないという利点があります。さらに、匿名関数を関数オブジェクトにすることもできます:

>>> f = lambda x: x * x
> ;>> f
at 0x101c6ef28>
>>> f(5)

同様に、次のような匿名関数も返されます。 def build(x, y ): return lambda: x * x + y * y

概要


Python では匿名関数のサポートが制限されており、匿名関数はいくつかの単純な場合にのみ使用できます。

部分関数

Python の functools モジュールには多くの便利な関数が用意されており、その 1 つが部分関数です。ここでの部分関数は数学的な意味での部分関数とは異なることに注意してください。

関数パラメータを導入する際に、パラメータのデフォルト値を設定することで関数呼び出しの難しさを軽減できると述べました。部分的な関数でもこれを行うことができます。例:

int() 関数は文字列を整数に変換できます。文字列のみが渡された場合、int() 関数はデフォルトで 10 進数に変換します。 )12345

ただし、int() 関数は追加の基本パラメータも提供し、デフォルト値は 10 です。 Base パラメータを渡すと、N ベース変換を実行できます:

>>> int('12345', Base=8)5349

>>> int('12345', 16) 74565

大量のバイナリ文字列を変換したいとします。毎回 int(x,base=2) を渡すのは非常に面倒なので、int2() の関数を定義して渡すことができると考えました。デフォルトではbase=2です:

def int2(x,base=2): return int(x,base)

このようにして、バイナリを変換すると非常に便利です:

>>> ; int2('1000000')64>> int2('1010101')85


functools.partial を使用すると、int2() を直接使用できます。次のコードを使用して新しい関数 int2 を作成します。 >> int2('1010101')85

簡単にまとめると、 functools.partial の機能は、関数の特定のパラメータを修正して (つまり、デフォルト値を設定して) 新しい関数を返すことになります。この新しい関数を呼び出すのが簡単になります。

上記の新しい int2 関数は基本パラメータをデフォルト値の 2 にリセットするだけですが、関数の呼び出し時に他の値も渡すことができることに注意してください:

>>> ,base=10)1000000


最後に、部分関数を作成するときに、関数オブジェクト、*args、**kw の 3 つのパラメーターを実際に受け取ることができます。 2 )

は、実際には int() 関数のキーワード パラメータのベースを修正します。つまり、

int2('10010')

は次と同等です:

kw = { 'base': 2 }int(' 10010 ', **kw)

を渡すと:

max2 = functools.partial(max, 10)

は実際には *args の一部として 10 を左側に自動的に追加します。つまり:

max2( 5, 6, 7)

は次と同等です:

args = (10, 5, 6, 7)
max(*args)

結果は 10 です。


概要

関数のパラメータが多すぎて簡略化する必要がある場合、functools.partial を使用して新しい関数を作成すると、元の関数のパラメータの一部を修正して呼び出しやすくなります。



以上がPython 上級: 高階関数の詳細な説明の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。