ホームページ  >  記事  >  バックエンド開発  >  Pythonでのクロージャ例の詳しい説明

Pythonでのクロージャ例の詳しい説明

WBOY
WBOYオリジナル
2016-06-16 08:42:231405ブラウズ

一般的に、クロージャの概念は多くの言語に関係しています。この記事では主に Python におけるクロージャの定義と関連する使用法について説明します。クロージャは主に Python で機能開発に使用されます。詳細な分析は次のとおりです:

1. 定義

Python のクロージャは、次のように表現的に定義 (解釈) されます。 内部関数で、外部スコープの変数 (グローバル スコープではない) が参照される場合、内部関数は相対的なクロージャとみなされます。他の定義ほど単純で理解しやすいです(これらの衒学的説明には、狂気を表すその他の馴染みのない用語がたくさんあり、初心者には適していません)。簡単な例を挙げて説明しましょう。

>>>def addx(x): 
>>> def adder(y): return x + y 
>>> return adder 
>>> c = addx(8) 
>>> type(c) 
<type 'function'> 
>>> c.__name__ 
'adder' 
>>> c(10) 
18

この単純なコードと定義を組み合わせて、クロージャを説明します。
内部関数内にある場合: adder(y) は内部関数です。
外部スコープ内の変数を参照します (ただし、グローバル スコープ内ではありません): x は参照される変数です。x は外部スコープ addx 内にありますが、グローバル スコープ内にはありません。
この内部関数加算器はクロージャです。

もう少し詳しく説明すると、関数を定義するときのクロージャ = 関数ブロック + 環境、adder が関数ブロック、x が環境であるということです。もちろん、単純な x だけではなく、多くの環境が存在する可能性があります。

2. クロージャを使用する際の注意点

1.外部スコープ内のローカル変数はクロージャ内で変更できません

>>> def foo(): 
...  m = 0 
...  def foo1(): 
...   m = 1 
...   print m 
... 
...  print m 
...  foo1() 
...  print m 
...
>>> foo()
0
1
0

実行結果からわかるように、クロージャ内でも変数mが定義されていますが、外部関数内のローカル変数mは変更されません。

2. 次のコードは、Python でクロージャを使用する場合の典型的なエラー コードです

def foo(): 
 a = 1 
 def bar(): 
  a = a + 1 
  return a 
 return bar

このプログラムの本来の目的は、クロージャー関数が呼び出されるたびに変数 a をインクリメントすることです。しかし、実際に使用する場合

>>> c = foo() 
>>> print c() 
Traceback (most recent call last): 
 File "<stdin>", line 1, in <module> 
 File "<stdin>", line 4, in bar 
UnboundLocalError: local variable 'a' referenced before assignment 

これは、コード c = foo() を実行するときに、Python がローカル変数を分析するためにすべてのクロージャ関数本体 bar() をインポートするためです。代入ステートメントの左側のすべての変数はローカル変数であると Python ルールで指定されています。次に、クロージャ bar() では、変数 a が代入記号「=」の左側にあり、Python によって bar() のローカル変数とみなされます。次に print c() が実行されると、プログラムは a = a + 1 まで実行されます。 a は事前に bar() のローカル変数として分類されているため、Python は bar の代入ステートメントの右側にある変数を探します。 ()。 a の値が見つからない場合は、エラーが報告されます。解決策は簡単です

def foo(): 
 a = [1] 
 def bar(): 
  a[0] = a[0] + 1 
  return a[0] 
 return bar

a をコンテナとして設定するだけです。これは使用するのがやや不快なので、python3 以降、a = a + 1 の前に非ローカル a ステートメントを使用するだけです。このステートメントは、a がクロージャのローカル変数ではないことを明示的に指定します。

3. Python クロージャを導入するときによく取り上げられる、エラーが発生しやすい例があります。このエラーがクロージャとあまり関係があるとは考えたこともありませんでしたが、確かに Python 関数のプログラミングは簡単です。間違えやすいのでここで紹介しておきます。以下のコードを見てみましょう

for i in range(3): 
 print i

このようなループ文はプログラムによく登場します。Python の問題は、ループが終了しても、ループ本体内の一時変数 i が破棄されず、実行環境に存在し続けることです。もう 1 つの Python 現象は、Python 関数は実行時に関数本体内の変数の値しか見つけられないことです。

flist = [] 
for i in range(3): 
 def foo(x): print x + i 
 flist.append(foo) 
for f in flist: 
 f(2)

このコードの実行結果は 2,3,4 になるはずだと思う人もいるかもしれませんが、実際の結果は 4,4,4 です。これは、関数が flist リストに追加されるとき、Python は i に値を割り当てていないためです。このとき、最初の for ループが終了した後でのみ、i の値が見つかります。 i は 2 です。つまり、上記のコードの実行結果は 4,4,4.
となります。 解決策も非常に簡単で、関数の定義を書き換えるだけです。

for i in range(3): 
 def foo(x,y=i): print x + y 
 flist.append(foo) 

3. 機能

ここまで述べたので、実際の開発においてこのクロージャは何に役立つのかと疑問に思う人もいるかもしれません。クロージャは主に機能開発中に使用されます。以下に 2 つのクロージャの主な用途について説明します。

目的 1: クロージャ実行後も、現在の動作環境を維持できます。

たとえば、関数の各実行の結果を、この関数の最後の実行の結果に基づくようにしたい場合。ボードゲームに似た例で説明しましょう。チェス盤のサイズが 50*50 で、左上隅が座標系の原点 (0,0) であると仮定します。この関数は、方向とステップの 2 つのパラメーターを受け取る関数が必要です。チェスの駒。方向とステップの長さに加えて、チェスの駒の動きの新しい座標も元の座標点に依存します。クロージャを使用して、チェスの駒の元の座標を維持することができます。

origin = [0, 0] # 坐标系统原点 
legal_x = [0, 50] # x轴方向的合法坐标 
legal_y = [0, 50] # y轴方向的合法坐标 
def create(pos=origin): 
 def player(direction,step): 
  # 这里应该首先判断参数direction,step的合法性,比如direction不能斜着走,step不能为负等 
  # 然后还要对新生成的x,y坐标的合法性进行判断处理,这里主要是想介绍闭包,就不详细写了。 
  new_x = pos[0] + direction[0]*step 
  new_y = pos[1] + direction[1]*step 
  pos[0] = new_x 
  pos[1] = new_y 
  #注意!此处不能写成 pos = [new_x, new_y],原因在上文有说过 
  return pos 
 return player 
 
player = create() # 创建棋子player,起点为原点 
print player([1,0],10) # 向x轴正方向移动10步 
print player([0,1],20) # 向y轴正方向移动20步 
print player([-1,0],10) # 向x轴负方向移动10步 

出力は次のとおりです:

[10, 0] 
[10, 20] 
[0, 20] 

用途2:闭包可以根据外部作用域的局部变量来得到不同的结果,这有点像一种类似配置功能的作用,我们可以修改外部的变量,闭包根据这个变量展现出不同的功能。比如有时我们需要对某些文件的特殊行进行分析,先要提取出这些特殊行。

def make_filter(keep): 
 def the_filter(file_name): 
  file = open(file_name) 
  lines = file.readlines() 
  file.close() 
  filter_doc = [i for i in lines if keep in i] 
  return filter_doc 
 return the_filter

如果我们需要取得文件"result.txt"中含有"pass"关键字的行,则可以这样使用例子程序

filter = make_filter("pass")
filter_result = filter("result.txt")

以上两种使用场景,用面向对象也是可以很简单的实现的,但是在用Python进行函数式编程时,闭包对数据的持久化以及按配置产生不同的功能,是很有帮助的。

相信本文所述对大家的Python程序设计有一定的借鉴价值。

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