ネストされたクラスのスコープ?


116

Pythonのネストされたクラスのスコープを理解しようとしています。これが私のコード例です:

class OuterClass:
    outer_var = 1
    class InnerClass:
        inner_var = outer_var

クラスの作成が完了せず、エラーが発生します。

<type 'exceptions.NameError'>: name 'outer_var' is not defined

試しinner_var = Outerclass.outer_varてもうまくいきません。私は得ます:

<type 'exceptions.NameError'>: name 'OuterClass' is not defined

outer_varからスタティックにアクセスしようとしていますInnerClass

これを行う方法はありますか?

回答:


105
class Outer(object):
    outer_var = 1

    class Inner(object):
        @property
        def inner_var(self):
            return Outer.outer_var

これは他の言語で同様に機能するものとはまったく異なり、へのアクセスをスコープする代わりにグローバルルックアップを使用しouter_varます。(名前Outerがバインドされているオブジェクトを変更すると、このコードは次に実行されるときにそのオブジェクトを使用します。)

代わりに、すべてのInnerオブジェクトにへの参照を持たせたい場合はOuterouter_varが実際にはインスタンス属性なので

class Outer(object):
    def __init__(self):
        self.outer_var = 1

    def get_inner(self):
        return self.Inner(self)
        # "self.Inner" is because Inner is a class attribute of this class
        # "Outer.Inner" would also work, or move Inner to global scope
        # and then just use "Inner"

    class Inner(object):
        def __init__(self, outer):
            self.outer = outer

        @property
        def inner_var(self):
            return self.outer.outer_var

Pythonではクラスのネストはあまり一般的ではなく、クラス間の特別な関係を自動的に暗示するものではないことに注意してください。入れ子にしない方がいいです。(必要に応じOuterInner、クラス属性をに設定することもできます。)


2
回答が機能するPythonのバージョンを追加すると役立つ場合があります。
AJ。

1
私は2.6 / 2.xを念頭に置いてこれを書きましたが、それを見ると、3.xでは同じように機能しないものは何もありません。

この部分の意味がよくわかりません、「(Outerという名前がバインドされているオブジェクトを変更した場合、このコードは次に実行されるときにそのオブジェクトを使用します。)」理解していただけますか?
batbrat 14年

1
@batbratは、参照するOuterたびに参照が新しく検索されることを意味しますInner.inner_var。したがって、名前Outerを新しいオブジェクトに再バインドすると、Inner.inner_varはその新しいオブジェクトを返し始めます。
フェリペ

45

私はあなたが簡単にできると思います:

class OuterClass:
    outer_var = 1

    class InnerClass:
        pass
    InnerClass.inner_var = outer_var

あなたが遭遇した問題はこれが原因です:

ブロックは、1つの単位として実行されるPythonプログラムテキストの一部です。ブロックは、モジュール、関数本体、およびクラス定義です。
(...)
スコープは、ブロック内の名前の可視性を定義します。
(...)
クラスブロックで定義された名前のスコープは、クラスブロックに限定されます。メソッドのコードブロックには適用されません。これは、関数スコープを使用して実装されるため、ジェネレータ式も含まれます。つまり、以下は失敗します。

   class A:  

       a = 42  

       b = list(a + i for i in range(10))

http://docs.python.org/reference/executionmodel.html#naming-and-binding

上記は
、関数本体がコードブロックでメソッドが関数である場合、クラス定義に存在する関数本体から定義された名前が関数本体に拡張されないことを意味します。

これを言い換えると
、クラス定義はコードブロックであり、外部クラス定義に存在する内部クラス定義から定義された名前は、内部クラス定義に拡張されません。


2
すごい。あなたの例は失敗し、「グローバル名 'a'は定義されていません」と主張します。それでも、リスト内包表記を置き換えると、[a + i for i in range(10)]Abが期待されるリストに正常にバインドされます[42..51]。
ジョージ

6
@George クラスAの例は私のものではないことに注意してください。これは、私がリンクを提供したPython公式ドキュメントからのものです。この例は失敗します。その失敗は、この例で表示したいものです。実際にlist(a + i for i in range(10))list((a + i for i in range(10)))それは言うことlist(a_generator)です。彼らは、ジェネレーターは関数のスコープと同じスコープで実装されていると言います。
eyquem

@George私にとって、それは、関数がモジュール内にあるかクラス内にあるかによって、関数の動作が異なることを意味しています。最初のケースでは、関数は外に出て、フリー識別子にバインドされたオブジェクトを見つけます。2番目のケースでは、関数、つまりメソッドは本体の外に出ません。モジュールの関数とクラスのメソッドは、実際には2種類のオブジェクトです。メソッドはクラスの関数だけではありません。それが私の考えです。
eyquem

5
@George:FWIW、list(...)呼び出しも理解もPython 3では機能しません。Py3 のドキュメントもこれを反映して少し異なります。「クラスブロックで定義された名前のスコープはクラスブロックに限定されます。メソッドのコードブロックには拡張されません。これは、関数スコープを使用して実装されるため、内包表記とジェネレータ式を含みます。」(強調鉱山)。
martineau 2013年

list(Aa + i for i in range(10))も機能しない理由に興味があります。AをAaで修正したところ、Aはグローバル名であると考えられます。
gaoxinge 2017

20

ネストされたクラスを使用しないだけの方が良いかもしれません。ネストする必要がある場合は、これを試してください:

x = 1
class OuterClass:
    outer_var = x
    class InnerClass:
        inner_var = x

または、ネストする前に両方のクラスを宣言します。

class OuterClass:
    outer_var = 1

class InnerClass:
    inner_var = OuterClass.outer_var

OuterClass.InnerClass = InnerClass

(この後、del InnerClass必要に応じて行うことができます。)


3

最も簡単な解決策:

class OuterClass:
    outer_var = 1
    class InnerClass:
        def __init__(self):
            self.inner_var = OuterClass.outer_var

それはあなたが明示的である必要がありますが、それほど努力をしません。


NameError:名前「OuterClass」は定義されていません
--1

3
ポイントは、外部クラススコープからアクセスすることです。さらに、外部内部の静的スコープから呼び出すと、同様のエラーが発生します。この投稿を削除することをお勧めします
Mr_and_Mrs_D

1

Pythonでは、可変オブジェクトは参照として渡されるため、外部クラスの参照を内部クラスに渡すことができます。

class OuterClass:
    def __init__(self):
        self.outer_var = 1
        self.inner_class = OuterClass.InnerClass(self)
        print('Inner variable in OuterClass = %d' % self.inner_class.inner_var)

    class InnerClass:
        def __init__(self, outer_class):
            self.outer_class = outer_class
            self.inner_var = 2
            print('Outer variable in InnerClass = %d' % self.outer_class.outer_var)

2
ここには参照サイクルがあり、一部のシナリオではこのクラスのインスタンスが解放されないことに注意してください。たとえば、cPythonで__del__ メソッドを定義した場合、ガベージコレクターは参照サイクルを処理できなくなり、オブジェクトはに入りgc.garbageます。上記のコードは現状のまま問題ありません。これに対処する方法は、弱い参照を使用することです。weakref(2.7)またはweakref(3.5)
guyarad

0

すべての説明はPythonのドキュメントにあります。Pythonチュートリアル

あなたの最初のエラーのために<type 'exceptions.NameError'>: name 'outer_var' is not defined。説明は次のとおりです。

メソッド内からデータ属性(または他のメソッド!)を参照するための省略形はありません。これにより、メソッドの可読性が実際に向上することがわかります。メソッドをざっと見たときに、ローカル変数とインスタンス変数を混​​同する可能性はありません。

Pythonチュートリアル9.4から引用

2番目のエラーについて <type 'exceptions.NameError'>: name 'OuterClass' is not defined

クラス定義が正常に(最後を介して)残されると、クラスオブジェクトが作成されます。

Pythonチュートリアル9.3.1から引用

だから、あなたがしようとしたときinner_var = Outerclass.outer_varQuterclassまだ作成されていない、理由ですname 'OuterClass' is not defined

最初のエラーのより詳細ですが面倒な説明:

ただし、クラスは囲み関数のスコープにアクセスできますが、クラス内にネストされたコードの囲みスコープとしては機能しません。Pythonは参照関数の囲み関数を検索しますが、囲みクラスは検索しません。つまり、クラスはローカルスコープであり、包含ローカルスコープにアクセスできますが、さらにネストされたコードの包含ローカルスコープとしては機能しません。

Learning.Python(5th).Mark.Lutzから引用

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.