【问题标题】:Python's initialization a list in a classPython初始化类中的列表
【发布时间】:2018-02-28 23:48:57
【问题描述】:

我不明白 python 的行为,我有很多相同的错误:

文件“./start.py”,第 20 行,在 print2
item.print2()
RuntimeError: 超出最大递归深度

我的代码如下:

#!/usr/bin/python

class Component:
    def print2(self):
        pass

class Composite(Component):
    items = []
    name = 'composite'

    def __init__(self, name):
        self.name = name

    def add(self, item):
        self.items.append(item)

    def print2(self):
        if self.items:
            for item in self.items:
                item.print2()
        else:
            print self.name

class Leaf(Component):
    name = 'leaf'

    def __init__(self, name):
        self.name = name

    def print2(self):
        print self.name

category = Composite('category1')
leaf = Composite('leaf1')
category.add(leaf)
leaf = Leaf('leaf2')
category.add(leaf)

category.print2()

当我在构造函数 (__init__) 中添加 self.items = [] 时,它工作正常。你能解释一下这种行为吗?

【问题讨论】:

  • 因为你所有的对象都共享items...这是一个类变量,即“静态”
  • __init__外的元素是属于类的静态元素,__init__内的元素是对象的成员,它们不属于类。这意味着每次调用print2(),无论对象是什么,self.items 都包含 2 个 Composite。

标签: python class


【解决方案1】:

items 是一个类变量,所以它被Composite 的每个实例共享。当您将append 转换为items 时,所有实例都会获得该项目。所以在添加leafs 之后,你有:

   Type                    Composite.items
------------------------------------------
 Composite    category1    [leaf1, leaf2]
                  |
 Composite      leaf1      [leaf1, leaf2]
                  |
      Leaf      leaf2

如果在leaf1 上调用print2,它将尝试在类中的每个items 上调用print2,其中包括leaf1 一次又一次,在无限递归中。

如果您改为在__init__ 中初始化self.items = [],它将是一个实例变量而不是类变量,因此每个Composite 将有自己的items 列表。

【讨论】:

    【解决方案2】:

    因为您所有的对象都在共享项目...这是一个类变量,即“静态”

    class Foo:
        # <anything assigned here is static>
        static_var1 = 'foo'
        def some_method(self, bar):
            self.x = bar # this is an *instance* variable
    

    所以在:

    def print2(self):
        if self.items:
            for item in self.items:
                item.print2()
    

    它遍历静态items。注意:

    In [11]: category.items
    Out[11]: [<__main__.Composite at 0x103078978>, <__main__.Leaf at 0x103078ac8>]
    
    In [12]: category.items[0].name
    Out[12]: 'leaf1'
    

    当您点击第一个项目时,它调用item.print2(),然后开始迭代items,然后再次点击'leaf1'再次调用item.print2()...

    注意,Python 的对象模型非常简单,instance.method() 等价于 InstanceClass.method(instance),即方法中发生的唯一“魔术”是,如果通过实例访问方法,则实际返回的是绑定方法,将实例绑定为函数的第一个参数。所以请注意:

    In [15]: class Foo:
        ...:     def mutate(self, x):
        ...:         self.foo = x
        ...:
    
    In [16]: f = Foo()
    
    In [17]: f.mutate(42)
    
    In [18]: f.foo
    Out[18]: 42
    
    In [19]: Foo.mutate(f, 99)
    
    In [20]: f.foo
    Out[20]: 99
    

    注意,Foo.mutate 只是一个函数。它并不关心 self 实际上是什么:

    In [21]: class Bar:
        ...:     pass
        ...:
    
    In [22]: b = Bar()
    
    In [23]: Foo.mutate(b, 'foooooo')
    
    In [24]: b.foo
    Out[24]: 'foooooo'
    

    一点奥秘,每次访问一个方法时,都会创建一个全新的绑定方法对象:

    In [34]: f.mutate
    Out[34]: <bound method Foo.mutate of <__main__.Foo object at 0x1030641d0>>
    
    In [35]: methods = f.mutate, f.mutate, f.mutate
    
    In [36]: [hex(id(m)) for m in methods]
    Out[36]: ['0x10581de08', '0x10581d208', '0x105abc988']
    

    这导致了看似奇怪的:

    In [37]: f.mutate is f.mutate
    Out[37]: False
    

    但是,它封装了相同的底层函数Foo.mutate,它不会改变,只是一个属性,就像类上的任何其他属性一样:

    In [38]: Foo.mutate is Foo.mutate
    Out[38]: True
    

    【讨论】:

      【解决方案3】:

      这里发生了两件事
      1. items 被创建为类变量
      2.leaf1被添加为组件:Composite('leaf1'),因此拥有类变量items

      这两个一起意味着leaf1.print2()将永远递归地调用自己

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2015-05-22
        • 1970-01-01
        • 2023-03-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-08-08
        相关资源
        最近更新 更多