【问题标题】:Nested classes' scope?嵌套类的范围?
【发布时间】:2010-12-18 10:49:21
【问题描述】:

我正在尝试了解 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

我正在尝试从InnerClass 访问静态outer_var

有没有办法做到这一点?

【问题讨论】:

    标签: python class scope nested inner-classes


    【解决方案1】:
    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。)

    【讨论】:

    • 添加您的答案适用的python版本可能会有所帮助。
    • 我写这篇文章时考虑到了 2.6/2.x,但是,看着它,我发现在 3.x 中没有什么不一样的。
    • 我不太明白你在这部分的意思,“(如果你改变名字Outer绑定的对象,那么这段代码将在下次执行时使用该对象。) “你能帮我理解一下吗?
    • @batbrat 这意味着每次执行Inner.inner_var 时都会重新查找对Outer 的引用。因此,如果您将名称 Outer 重新绑定到一个新对象,Inner.inner_var 将开始返回该新对象。
    【解决方案2】:

    我认为你可以这样做:

    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

    以上意思是:
    函数体是代码块,方法是函数,则在类定义中存在的函数体之外定义的名称不会扩展到函数体。

    根据您的情况解释一下:
    类定义是一个代码块,则在外部类定义中存在的内部类定义之外定义的名称不会扩展到内部类定义。

    【讨论】:

    • 太棒了。您的示例失败,声称“未定义全局名称'a'”。然而,替换列表理解 [a + i for i in range(10)] 成功地将 A.b 绑定到预期列表 [42..51]。
    • @George 请注意,带有 class A 的示例不是我的,它来自我提供链接的 Python 官方文档。这个例子失败了,这个失败就是这个例子想要展示的。其实list(a + i for i in range(10))就是list((a + i for i in range(10))),也就是说list(a_generator)。他们说生成器的实现范围与函数的范围相似。
    • @George 对我来说,这意味着函数在模块或类中的行为会有所不同。在第一种情况下,一个函数到外面去寻找绑定到一个空闲标识符的对象。在第二种情况下,一个函数,也就是一个方法,不会超出它的主体。模块中的函数和类中的方法实际上是两种对象。方法不仅仅是类中的函数。这是我的想法。
    • @George:FWIW,list(...) 调用和理解在 Python 3 中都不起作用。Py3 的 documentation 也略有不同,反映了这一点。它现在说“在类块中定义的名称范围仅限于类块;它不会扩展到方法的代码块——这包括 comprehensions 和生成器表达式,因为它们是使用函数范围实现。”(强调我的)。
    • 我很好奇为什么 list(A.a + i for i in range(10)) 也不起作用,我修复了 a by A.a.我认为 A 可能是一个全局名称。
    【解决方案3】:

    如果你不使用嵌套类,你可能会更好。如果你必须嵌套,试试这个:

    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。)

    【讨论】:

      【解决方案4】:

      最简单的解决方案:

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

      它要求你是明确的,但不需要太多的努力。

      【讨论】:

      • NameError: name 'OuterClass' is not defined - -1
      • 重点是从外部类作用域访问它——如果你从外部类的静态作用域调用它,也会发生类似的错误。我建议你删除这篇文章
      【解决方案5】:

      在 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)
      

      【讨论】:

      • 请注意这里有一个引用循环,并且在某些场景下这个类的实例不会被释放。例如,使用 cPython,如果您定义了 __del__ 方法,垃圾收集器将无法处理引用循环,并且对象将进入 gc.garbage。上面的代码原样没有问题。处理它的方法是使用弱引用。您可以阅读weakref (2.7)weakref (3.5) 上的文档
      【解决方案6】:

      所有的解释都可以在Python Documentation The Python Tutorial中找到

      对于您的第一个错误&lt;type 'exceptions.NameError'&gt;: name 'outer_var' is not defined。解释是:

      从方法中引用数据属性(或其他方法!)没有简写。我发现这实际上增加了方法的可读性:浏览方法时不会混淆局部变量和实例变量。

      引自Python Tutorial 9.4

      第二个错误&lt;type 'exceptions.NameError'&gt;: name 'OuterClass' is not defined

      当一个类定义正常离开时(通过结尾),一个类对象被创建。

      引自Python Tutorial 9.3.1

      所以当您尝试inner_var = Outerclass.outer_var 时,Quterclass 尚未创建,这就是name 'OuterClass' is not defined 的原因

      对您的第一个错误的更详细但乏味的解释:

      虽然类可以访问封闭函数的作用域,但它们不会起作用 作为嵌套在类中的代码的封闭范围:Python 搜索封闭函数 对于引用的名称,但绝不是任何封闭类。也就是说,一个类是一个局部作用域 并且可以访问封闭的本地范围,但它不能用作封闭的本地范围 进一步嵌套代码。

      引自Learning.Python(5th).Mark.Lutz

      【讨论】:

        【解决方案7】:
        class c_outer:
            def __init__(self, name:str='default_name'):
                self._name = name
                self._instance_lst = list()
                self._x = self.c_inner()
        
            def get_name(self):
                return(self._name)
        
            def add_inner_instance(self,name:str='default'):
                self._instance_lst.append(self.c_inner(name))
        
            def get_instance_name(self,index:int):
                return(self._instance_lst[index].get_name())
        
        
            class c_inner:
                def __init__(self, name:str='default_name'):
                    self._name = name
                def get_name(self):
                    return(self._name)
        
        
        outer = c_outer("name_outer")
        
        outer.add_inner_instance("test1")
        outer.add_inner_instance("test2")
        outer.add_inner_instance("test3")
        inner_1 = outer.c_inner("name_inner1")
        inner_2 = outer.c_inner("name_inner2")
        inner_3 = outer.c_inner("name_inner3")
        
        print(outer.get_instance_name(index=0))
        print(outer.get_instance_name(1))
        print(outer._instance_lst[2]._name
        print(outer.get_name())
        print(inner_1.get_name())
        print(inner_2.get_name())
        

        测试1 测试2 测试3 name_outer name_inner1 name_inner2 name_inner3

        【讨论】:

          猜你喜欢
          • 2022-12-01
          • 1970-01-01
          • 2019-04-10
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-01-01
          • 2018-05-23
          相关资源
          最近更新 更多