web-dev-qa-db-ja.com

Python 2、親スコープの変数に書き込むにはどうすればよいですか?

関数内に次のコードがあります:

stored_blocks = {}
def replace_blocks(m):
    block = m.group(0)
    block_hash = sha1(block)
    stored_blocks[block_hash] = block
    return '{{{%s}}}' % block_hash

num_converted = 0
def convert_variables(m):
    name = m.group(1)
    num_converted += 1
    return '<%%= %s %%>' % name

fixed = MATCH_DECLARE_NEW.sub('', template)
fixed = MATCH_PYTHON_BLOCK.sub(replace_blocks, fixed)
fixed = MATCH_FORMAT.sub(convert_variables, fixed)

stored_blocksへの要素の追加は正常に機能しますが、2番目のサブ関数でnum_convertedを増やすことはできません。

UnboundLocalError:割り当て前に参照されるローカル変数 'num_converted'

globalを使用できますが、グローバル変数はいため、その変数がグローバルである必要はまったくありません。

したがって、親関数のスコープ内の変数にどのように書き込むことができるのか興味があります。 nonlocal num_convertedはおそらく仕事をしますが、Python 2.x.

77
ThiefMaster

問題:これは、Pythonのスコープ規則が認知されていないためです。 +=代入演算子の存在は、ターゲットnum_convertedを囲んでいる関数のスコープに対してローカルとしてマークし、Python 2.x to globalキーワードのみが現在のスコープから変数参照を持ち上げることができ、トップに直接移動できます。

修正:num_convertedを単一要素の配列に変換します。

num_converted = [0]
def convert_variables(m):
    name = m.group(1)
    num_converted[0] += 1
    return '<%%= %s %%>' % name
86
Marcelo Cantos

(編集された回答については以下を参照)

次のようなものを使用できます。

def convert_variables(m):
    name = m.group(1)
    convert_variables.num_converted += 1
    return '<%%= %s %%>' % name

convert_variables.num_converted = 0

こちらです、 num_convertedは、convert_variableメソッドのCのような「静的」変数として機能します。


(編集済み)

def convert_variables(m):
    name = m.group(1)
    convert_variables.num_converted = convert_variables.__dict__.get("num_converted", 0) + 1
    return '<%%= %s %%>' % name

このように、メインプロシージャでカウンターを初期化する必要はありません。

29
PabloG

globalキーワードを使用しても問題ありません。あなたが書く場合:

num_converted = 0
def convert_variables(m):
    global num_converted
    name = m.group(1)
    num_converted += 1
    return '<%%= %s %%>' % name

... num_convertedは「グローバル変数」にはなりません(つまり、他の予期しない場所では表示されません)。これは、convert_variables内で変更できることを意味します。それはまさにあなたが望むもののようです。

別の言い方をすれば、num_convertedalreadyグローバル変数です。すべてのglobal num_converted構文はtell Python "この関数の内部では、ローカルnum_converted変数を作成せずに、既存のグローバル変数を使用します。

9
Emile

クラスインスタンスを使用して状態を保持するのはどうですか?クラスをインスタンス化し、インスタンスメソッドをsubsに渡すと、それらの関数はselfへの参照を持ちます...

7
Seb

いくつかの発言があります。

まず、xml.parsers.expatなどのライブラリで使用されているように、生のコールバックを処理するときに、このようなネストされた関数の1つのアプリケーションが表示されます。 (ライブラリ作成者がこのアプローチを選択したことは好ましくないかもしれませんが、それでも…それを使用する理由があります。)

2番目:クラス内では、配列(num_converted [0])に代わるより優れた選択肢があります。これがセバスチャンが言っていたことだと思います。

class MainClass:
    _num_converted = 0
    def outer_method( self ):
        def convert_variables(m):
            name = m.group(1)
            self._num_converted += 1
            return '<%%= %s %%>' % name

コード内でコメントに値するのはまだ奇妙です...しかし、変数は少なくともクラスに対してローカルです。

6
Steve White

変更元: https://stackoverflow.com/a/40690954/819544

inspectモジュールを活用して、呼び出しスコープのグローバルdictにアクセスし、そこに書き込むことができます。つまり、このトリックを利用して、インポートされたサブモジュールで定義されたネストされた関数から呼び出しスコープにアクセスすることさえできます。

import inspect 

def get_globals(scope_level=0):
    return dict(inspect.getmembers(inspect.stack()[scope_level][0]))["f_globals"]

num_converted = 0
def foobar():
    get_globals(0)['num_converted'] += 1

foobar()
print(num_converted) 
# 1

scope_level引数は必要に応じて。設定scope_level=1は、サブモジュールで定義された関数、scope_level=2サブモジュールなどのデコレータで定義された内部関数.

注意:canできるからといって、そうすべきだとは限りません。

0
David Marx