1. ホーム
  2. python

[解決済み] 関数内にクラスを作成し、包含関数のスコープで定義された関数にアクセスする

2023-05-31 15:33:44

質問

編集 :

この質問の下にある私の全回答を参照してください。

tl;drの答え : Pythonは静的にネストされたスコープを持ちます。そのため 静的 アスペクトは暗黙の変数宣言と相互作用し、明白でない結果をもたらす可能性があります。

(これは言語の一般的な動的性質のために特に驚くべきことかもしれません)。

私はPythonのスコープルールについてかなり良いハンドルを持っていると思っていましたが、この問題は私を完全に窒息させ、私のGoogle-fuは私を失敗させました(私は驚いていない - 質問のタイトルを見てください;)。

私は期待通りに動作するいくつかの例から始めるつもりですが、面白い部分のために例4まで自由に飛ばしてください。

例1.

>>> x = 3
>>> class MyClass(object):
...     x = x
... 
>>> MyClass.x
3

クラスの定義中に、外部(この場合はグローバル)スコープで定義された変数にアクセスすることができる、という単純なものです。

例2.

>>> def mymethod(self):
...     return self.x
... 
>>> x = 3
>>> class MyClass(object):
...     x = x
...     mymethod = mymethod
...
>>> MyClass().mymethod()
3

もう一度(とりあえず無視して なぜ を無視して)、ここでは何も予想外のことはありません。

注意 Frédéric が指摘するように、この関数は動作しないようです。代わりに例5(以降)を参照してください。

例3.

>>> def myfunc():
...     x = 3
...     class MyClass(object):
...         x = x
...     return MyClass
... 
>>> myfunc().x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in myfunc
  File "<stdin>", line 4, in MyClass
NameError: name 'x' is not defined

これは基本的に例 1 と同じです。クラス定義の内部から外部のスコープにアクセスしていますが、今回はそのスコープがグローバルでないため myfunc() .

5を編集します。 として @user3022222 さんのご指摘の通り のように、私は最初の投稿でこの例を失敗させました。これは、関数だけが (このクラス定義のような他のコードブロックではなく) 囲んでいるスコープ内の変数にアクセスできるために失敗したのだと思います。関数以外のコードブロックでは、ローカル変数、グローバル変数、組み込み変数のみにアクセス可能です。より詳細な説明は この質問

もう1つ

例4.

>>> def my_defining_func():
...     def mymethod(self):
...         return self.y
...     class MyClass(object):
...         mymethod = mymethod
...         y = 3
...     return MyClass
... 
>>> my_defining_func()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in my_defining_func
  File "<stdin>", line 5, in MyClass
NameError: name 'mymethod' is not defined

えっと...失礼します。

例2と何が違うのでしょうか?

完全に混乱しています。解決してください。 ありがとうございます。

P.S. 私の理解の問題だけではない可能性もありますが、私はこれを Python 2.5.2 と Python 2.6.2 で試しました。残念ながら、これらは私が現在アクセスできるすべてですが、それらは両方とも同じ挙動を示します。

編集 によると http://docs.python.org/tutorial/classes.html#python-scopes-and-namespaces によると、実行中のいつでも、名前空間に直接アクセスできる少なくとも3つのネストされたスコープが存在します。

  • 最も内側のスコープで、最初に検索されます。 最初に検索される最も内側のスコープには、ローカル の名前
  • を囲むすべての 関数のスコープがあり、それは 最も近い スコープには、ローカルでない名前だけでなく 非グローバル名
  • 最後から 2 番目のスコープには 現在のモジュールのグローバル名
  • 一番外側のスコープ (最後に検索されます) を含む名前空間です。 の名前です。

#4.は、このうち2番目の反例と思われます。

編集2

例5.

>>> def fun1():
...     x = 3
...     def fun2():
...         print x
...     return fun2
... 
>>> fun1()()
3

3を編集

Frédéric が指摘したように、外部スコープで持っているのと同じ名前の変数への代入は、外部変数を "mask" して、代入が機能するのを妨げているようです。

というわけで、例 4 のこの修正バージョンは動作します。

def my_defining_func():
    def mymethod_outer(self):
        return self.y
    class MyClass(object):
        mymethod = mymethod_outer
        y = 3
    return MyClass

my_defining_func()

しかし、これはそうではありません。

def my_defining_func():
    def mymethod(self):
        return self.y
    class MyClass(object):
        mymethod_temp = mymethod
        mymethod = mymethod_temp
        y = 3
    return MyClass

my_defining_func()

なぜこのようなマスクが発生するのか、まだ完全に理解できていません。

この例は、少なくとも何らかのヒント(およびより有用なエラーメッセージ)を提供します。

>>> def my_defining_func():
...     x = 3
...     def my_inner_func():
...         x = x
...         return x
...     return my_inner_func
... 
>>> my_defining_func()()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in my_inner_func
UnboundLocalError: local variable 'x' referenced before assignment
>>> my_defining_func()
<function my_inner_func at 0xb755e6f4>

つまり、ローカル変数は関数作成時に定義され (これは成功します)、その結果ローカル名が "reserved" となり、関数が呼び出されたときに外部スコープ名がマスクされるようです。

興味深いですね。

Frédéricさん、回答ありがとうございました。

参考までに、以下より pythonのドキュメント :

<ブロッククオート

スコープがテキストで決定されることを理解することは重要です。 はテキストで決定されることを理解することが重要です。 モジュールで定義された関数のグローバルスコープは モジュールで定義された関数のグローバルスコープは、そのモジュールの名前空間です。 モジュールで定義された関数のグローバルスコープはそのモジュールの名前空間であり、その関数がどこから、あるいはどのようなエイリアスで呼ばれたとしても 関数が呼び出されたとしても、そのグローバルスコープはそのモジュールの名前空間です。一方 実際の名前の検索は実行時に動的に行われます 実行時に動的に行われる。 言語定義は、静的な名前解決に向かって進化しています。 しかし、言語定義は静的な名前解決に向かって進化しています。 「動的な名前解決に頼らないでください。 動的な名前解決に頼ってはいけません。(実際 ローカル変数はすでに静的に決定されています 静的に決定されます)。

4を編集

本当の答え

この一見わかりにくい挙動は、Pythonの の静的ネストされたスコープが原因です。 . とは何の関係もありません。 PEP 3104 .

PEP227より。

名前解決のルールは典型的なものです。 静的スコープを持つ言語の典型的なものです [...]. [ただし] 変数は宣言されない。 名前バインディング操作が関数内のどこかで発生した場合 関数内の任意の場所で名前バインディング操作が発生した場合、その名前 はその関数のローカルなものとして扱われ として扱われ,すべての参照はそのローカルな を参照します。 名前がバインドされる前に参照が発生した場合 が発生した場合、NameErrorが発生します。 が発生します。

[...]

Tim Peters 氏の例では、宣言がない場合にスコープを入れ子にすることの潜在的な落とし穴が示されています。 Tim Peters の例では、宣言がない場合にスコープを入れ子にすることの潜在的な落とし穴を示しています。

i = 6
def f(x):
    def g():
        print i
    # ...
    # skip to the next page
    # ...
    for i in x:  # ah, i *is* local to f, so this is what g sees
        pass
    g()

g()の呼び出しは、f()でforループによって束縛された変数iを参照することになります。 ループによってf()に束縛された変数iを参照します。 ループが実行される前にg()が呼び出された場合、NameErrorが発生します。 が発生します。

Timの例の簡単なバージョンを2つ実行してみましょう。

>>> i = 6
>>> def f(x):
...     def g():
...             print i
...     # ...
...     # later
...     # ...
...     i = x
...     g()
... 
>>> f(3)
3

いつ g() が見つからない場合 i が見つからない場合は、動的に外側を検索し、内側のスコープにある if のスコープにバインドされている 3 にバインドされています。 i = x の代入が必要です。

の最後の2つの文の順番を変えることで f の最後の2つの文の順番を変えるとエラーになります。

>>> i = 6
>>> def f(x):
...     def g():
...             print i
...     # ...
...     # later
...     # ...
...     g()
...     i = x  # Note: I've swapped places
... 
>>> f(3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 7, in f
  File "<stdin>", line 3, in g
NameError: free variable 'i' referenced before assignment in enclosing scope

PEP227に「名前解決のルールは静的スコープを持つ言語の典型である」と書かれていたことを思い出し、(半)等価なC言語版の提供物をみてみましょう。

// nested.c
#include <stdio.h>

int i = 6;
void f(int x){
    int i;  // <--- implicit in the python code above
    void g(){
        printf("%d\n",i);
    }
    g();
    i = x;
    g();
}

int main(void){
    f(3);
}

をコンパイルして実行します。

$ gcc nested.c -o nested
$ ./nested 
134520820
3

C言語では結合されていない変数を喜んで使いますが、Pythonでは(ありがたいことに)拒否されます。

面白い余談ですが、静的にネストされたスコープによって Alex Martelli が呼び出した 関数のローカル変数はディクショナリーではなく、値のタイトなベクトルに保持され、各ローカル変数へのアクセスは名前検索ではなく、そのベクトルのインデックスを使用します。

どのように解決するのですか?

それはPythonの名前解決ルールのせいです。 の場合、グローバルスコープとローカルスコープにしかアクセスできず、その中間のスコープ、例えば自分のすぐ外側のスコープにはアクセスできません。

EDITです。 上記は言葉足らずでしたね、あなた する は外部スコープで定義された変数にアクセスすることができますが x = x または mymethod = mymethod をグローバルでない名前空間から呼び出した場合、実際には外側の変数をローカルに定義している変数でマスクしていることになります。

例 2 では、直前の外部スコープがグローバルスコープであるため MyClass を見ることができます。 mymethod と表示されますが、例4では、直前の外部スコープが my_defining_func() の外側で定義されているので、それはできません。 mymethod はすでにそのローカル定義によってマスクされているため、できません。

参照 PEP 3104 を参照してください。

また、上記の理由により、Python 2.6.5 または 3.1.2 のいずれでも example 3 を実行することができないことに注意してください。

>>> def myfunc():
...     x = 3
...     class MyClass(object):
...         x = x
...     return MyClass
... 
>>> myfunc().x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in myfunc
  File "<stdin>", line 4, in MyClass
NameError: name 'x' is not defined

しかし、次のようにすればうまくいくでしょう。

>>> def myfunc():
...     x = 3
...     class MyClass(object):
...         y = x
...     return MyClass
... 
>>> myfunc().y
3