16

次のクロージャー関数は、javascript で正常に動作します。

function generateNextNumber(startNumber) {
    var current = startNumber;
    return function(){
        return current += 1;
    }
}

var getNextNumber = generateNextNumber(10);
for (var i = 0; i < 10; i++) {
    console.log(getNextNumber());
}

私はPythonで同じことをしようとしました

def generateNextNumber(startNumber):
    current = startNumber
    def tempFunction():
        current += 1
        return current
    return tempFunction

getNextNumber = generateNextNumber(10)
for i in range(10):
    print (getNextNumber())

次のエラーが表示されます

Traceback (most recent call last):
  File "/home/thefourtheye/Desktop/Test1.py", line 10, in <module>
    print (getNextNumber())
  File "/home/thefourtheye/Desktop/Test1.py", line 4, in tempFunction
    current += 1
UnboundLocalError: local variable 'current' referenced before assignment

と を印刷したところvars()locals()が存在tempFunctionすることが確認されましたcurrent

({'current': 10}, {'current': 10})

しかし、このようにプログラムを少し変更すると

def generateNextNumber(startNumber):
    current = {"Number" : startNumber}
    def tempFunction():
        current["Number"] += 1
        return current["Number"]
    return tempFunction

できます。これが機能する理由を説明できません。誰か説明してくれませんか?

4

2 に答える 2

23

Python は、関数内のすべての変数がローカルであると想定しています。これは、同じ名前のグローバル変数、または外側のスコープでの誤った使用を避けるためです。いくつかの重要な点で、この違いは、Python ではローカル変数宣言が自動/暗黙的であるのに対し、JavaScript ではそうではない (を使用する必要があるvar) という事実の結果です。ソリューション:

global宣言を使用する

def generateNextNumber(startNumber):
    global current
    current= startNumber
    def tempFunction():
        global current
        current += 1
        return current 
    return tempFunction

tempFunction場合によっては有効ですが、同時にアクティブにできるのは の 1 つのインスタンスのみです。

関数属性を使用する

def generateNextNumber(startNumber):
    def tempFunction():
        tempFunction.current += 1
        return tempFunction.current
    tempFunction.current= startNumber
    return tempFunction

関数はオブジェクト (したがって属性を持つことができる) であり、宣言時にインスタンス化され、外側の関数 (またはモジュール。この場合は実際にはグローバル) に対してローカルになるという事実を使用します。これは、名前が「メンバーアクセス」演算子をtempFunction使用して独自の定義内で初めて使用され、ローカルと見なされないため、機能します。."call"()および "element access"[]演算子でも同様のことが起こります。後者のケースは、コードが機能する理由を説明しています。

名前を強制的に非ローカルと見なす

def generateNextNumber(startNumber):
    current= type("OnTheFly",(),{})()
    current.value= startNumber
    def tempFunction():
        current.value += 1
        return current.value
    return tempFunction

これについては、前のセクションですでに説明しました。メンバーアクセス演算子を使用することで、 .「既に存在する」と言っているcurrentため、囲んでいるスコープで検索されます。この特定のケースでは、type関数を使用してクラスを作成し、すぐにそのインスタンスを作成しています (2 番目の括弧のセットを使用)。一般的なオブジェクトの代わりに、リストまたは辞書を使用することもできます。2 番目のケースは、非常に一般的な解決策でした。

関数オブジェクトを使用する

def generateNextNumber(startNumber):
    class TempFunction:
        def __call__(self):
            self.current += 1
            return self.current
    tempFunction= TempFunction()
    tempFunction.current= startNumber
    return tempFunction

クラスにcallメソッドあるオブジェクトはすべて関数であるため、関数呼び出し演算子で呼び出すことができます()。これは、前の 2 つのケースと非常に関連しています。

nonlocal宣言を使用する

def generateNextNumber(startNumber):
    current= startNumber
    def tempFunction():
        nonlocal current
        current += 1
        return current
    return tempFunction

それが意味するのと同じようにglobal...まあ、グローバルnonlocalは「直前のスコープ内」を意味します。Python 3 およびおそらくそれ以降のバージョンの Python 2 で有効です。

ジェネレーターを使用する

def generateNextNumber(current):
    while True :
        current+= 1
        yield current

これはおそらく、非ローカル変数アクセスの一般的な問題ではなく、説明に使用した特定のケースにアプローチするための最も「Pythonic」な方法です。言わずにはいられませんでした。ただし、マイナーな変更を加えて呼び出す必要があります。

getNextNumber = generateNextNumber(10)
for i in range(10):
    print (getNextNumber.next())

forへの呼び出しを駆動するときnext()は暗黙的です(ただし、ジェネレーターは私の例のように無限にすることはできません)。

于 2013-08-29T06:05:17.937 に答える
3

nonlocalPython は、またはが宣言されていない限り、関数に割り当てが含まれる変数はすべてローカルであると定義することにより、関数のローカル変数が何であるかを決定しますglobal。したがって、

current += 1

非ローカル変数を非表示にするという名前のローカル変数を作成しcurrentます。Python 2 を使用している場合、標準的な解決策は (これを行わないようにする以外に) current、1 要素のリストを作成して使用することです。

current[0] += 1

参考までに、「これをしないようにしようとしています」は、次のようになります。

class Counter(object):
    def __init__(self):
        self.count = 0
    def __call__(self):
        self.count += 1
        return self.count
c = Counter()
c()  # Returns 1
c()  # Returns 2
于 2013-08-29T04:10:23.583 に答える