ขอบเขตของคลาสที่ซ้อนกัน?


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อบเจ็กต์ทั้งหมดอ้างอิงไปยังOuterเนื่องจากouter_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 และไม่ได้หมายความถึงความสัมพันธ์พิเศษใด ๆ ระหว่างคลาสโดยอัตโนมัติ คุณไม่ควรทำรัง (คุณยังสามารถตั้งค่าแอตทริบิวต์ระดับบนOuterไปInnerถ้าคุณต้องการ.)


2
อาจเป็นประโยชน์หากเพิ่มเวอร์ชันของ python คำตอบของคุณจะใช้งานได้
AJ.

1
ฉันเขียนสิ่งนี้โดยคำนึงถึง 2.6 / 2.x แต่เมื่อมองไปที่มันฉันไม่เห็นอะไรที่จะไม่ทำงานเหมือนเดิมใน 3.x

ฉันไม่ค่อยเข้าใจว่าคุณหมายถึงอะไรในส่วนนี้ "(ถ้าคุณเปลี่ยนสิ่งที่ชื่อ Outer ถูกผูกไว้รหัสนี้จะใช้วัตถุนั้นในครั้งถัดไปที่เรียกใช้งาน)" คุณช่วยฉันเข้าใจได้ไหม
batbrat

1
@batbrat ก็หมายความว่าการอ้างอิงถึงจะเงยหน้าขึ้นมองใหม่ทุกครั้งที่คุณทำOuter Inner.inner_varดังนั้นหากคุณผูกชื่อOuterใหม่กับวัตถุใหม่Inner.inner_varจะเริ่มคืนวัตถุใหม่นั้น
เฟลิเป้

45

ฉันคิดว่าคุณสามารถทำได้:

class OuterClass:
    outer_var = 1

    class InnerClass:
        pass
    InnerClass.inner_var = outer_var

ปัญหาที่คุณพบเกิดจากสิ่งนี้:

บล็อกคือส่วนของข้อความโปรแกรม 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]
George

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 สำหรับฉันนั่นหมายความว่าฟังก์ชันจะทำงานแตกต่างกันไปตามว่าอยู่ในโมดูลหรือในคลาส ในกรณีแรกฟังก์ชันจะออกไปข้างนอกเพื่อค้นหาวัตถุที่ผูกกับตัวระบุอิสระ ในกรณีที่สองฟังก์ชันกล่าวคือวิธีการไม่ได้ออกไปนอกร่างกาย ฟังก์ชั่นในโมดูลและวิธีการในชั้นเรียนนั้นในความเป็นจริงเป็นวัตถุสองชนิด วิธีการไม่ได้เป็นเพียงฟังก์ชันในชั้นเรียนเท่านั้น นั่นคือความคิดของฉัน
eyquem

5
@ จอร์จ: FWIW ไม่ว่าการlist(...)โทรหรือการทำความเข้าใจจะไม่ทำงานใน Python 3 เอกสารประกอบสำหรับ Py3 ก็แตกต่างกันเล็กน้อยเพื่อสะท้อนสิ่งนี้ ตอนนี้ระบุว่า " ขอบเขตของชื่อที่กำหนดในบล็อกคลาสนั้น จำกัด อยู่ที่บล็อกคลาสเท่านั้น แต่จะไม่ขยายไปถึงบล็อกโค้ดของวิธีการซึ่งรวมถึงความเข้าใจและนิพจน์ตัวสร้างเนื่องจากมีการใช้งานโดยใช้ขอบเขตฟังก์ชัน " (เน้นของฉัน )
martineau

ฉันสงสัยว่าทำไมรายการ (Aa + i สำหรับฉันในช่วง (10)) จึงไม่ทำงานซึ่งฉันแก้ไขโดย Aa ฉันคิดว่า A อาจเป็นชื่อสากล
gaoxinge

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
Mr_and_Mrs_D

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รหัสด้านบนตามที่เป็นอยู่นั้นไม่มีปัญหา วิธีที่จะจัดการกับมันใช้อ้างอิงอ่อนแอ คุณสามารถอ่านเอกสารเกี่ยวกับจุดอ่อน (2.7)หรือจุดอ่อน (3.5)
Guyarad

0

คำอธิบายทั้งหมดสามารถพบได้ในPython Documentation The Python Tutorial

<type 'exceptions.NameError'>: name 'outer_var' is not definedสำหรับข้อผิดพลาดครั้งแรกของคุณ คำอธิบายคือ:

ไม่มีชวเลขสำหรับอ้างอิงแอตทริบิวต์ข้อมูล (หรือวิธีการอื่น ๆ !) จากภายในวิธีการ ฉันพบว่าสิ่งนี้ช่วยเพิ่มความสามารถในการอ่านเมธอด: ไม่มีโอกาสที่จะทำให้ตัวแปรโลคัลและตัวแปรอินสแตนซ์สับสนเมื่อมองผ่านเมธอด

ยกมาจากThe Python Tutorial 9.4

สำหรับข้อผิดพลาดที่สองของคุณ <type 'exceptions.NameError'>: name 'OuterClass' is not defined

เมื่อนิยามคลาสถูกปล่อยทิ้งไว้ตามปกติ (ผ่านส่วนท้าย) คลาสอ็อบเจ็กต์จะถูกสร้างขึ้น

ยกมาจากThe Python Tutorial 9.3.1

ดังนั้นเมื่อคุณลองinner_var = Outerclass.outer_varแล้วQuterclassยังไม่ได้สร้างนั่นคือเหตุผลname 'OuterClass' is not defined

คำอธิบายที่ละเอียดกว่า แต่น่าเบื่อสำหรับข้อผิดพลาดแรกของคุณ:

แม้ว่าคลาสจะสามารถเข้าถึงขอบเขตของฟังก์ชันที่ปิดล้อมได้ แต่ก็ไม่ได้ทำหน้าที่ปิดขอบเขตขอบเขตของโค้ดที่ซ้อนอยู่ภายในคลาส: Python ค้นหาฟังก์ชันที่ปิดล้อมสำหรับชื่อที่อ้างอิง แต่ไม่เคยปิดคลาสใด ๆ นั่นคือคลาสเป็นขอบเขตแบบโลคัลและสามารถเข้าถึงการปิดขอบเขตโลคัลได้ แต่ไม่ได้ทำหน้าที่เป็นขอบเขตโลคัลที่ปิดล้อมเพื่อโค้ดที่ซ้อนกันเพิ่มเติม

อ้างจากLearning.Python (5th) .Mark.Lutz

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.