在 super() 上使用 **kwargs 给我属性错误

Using **kwargs on super() gives me Attribute Error

只是想创建一个简单的玩具示例来计算金字塔的表面积:

class Rectangle:
    def __init__(self, length, width, **kwargs):
        self.length = length
        self.width = width
        #super().__init__(**kwargs)
    def area(self):
        return self.length * self.width
    def perim(self):
        return 2 * (self.length + self.width)

class Square(Rectangle):
    def __init__(self, length, **kwargs):
        super().__init__(length = length, width = length, **kwargs)

class Triangle:
    def __init__(self, base, height, **kwargs):
        self.base = base
        self.height = height
        super().__init__(**kwargs)
    def tri_area(self):
        return 0.5 * self.base * self.height

class Pyramid(Square, Triangle):
    def __init__(self, base, slant, **kwargs):
        kwargs["height"] = slant
        kwargs["length"] = base
        super().__init__(base = base, **kwargs)

    def surf_area(self):
        return super().area() + 4 * super().tri_area()

p = Pyramid(2,4)
p.surf_area()

但这给了我一个

的错误
AttributeError                            Traceback (most recent call last)
<ipython-input-154-d97bd6ab2312> in <module>
      1 p = Pyramid(2,4)
----> 2 p.surf_area()

<ipython-input-153-457095747484> in surf_area(self)
      6 
      7     def surf_area(self):
----> 8         return super().area() + 4 * super().tri_area()

<ipython-input-151-8b1d4ef9dca9> in tri_area(self)
      5         super().__init__(**kwargs)
      6     def tri_area(self):
----> 7         return 0.5 * self.base * self.height

AttributeError: 'Pyramid' object has no attribute 'base'

在线资源似乎并没有很好地提供对 **kwargs 的概念性理解(或者它们以对初学者来说过于迷宫的方式编写)。这是否与 **kwargs 作为可迭代对象在进行下一次调用之前需要完全耗尽这一事实有关?

我能理解您为什么会感到困惑。要实现的主要技巧是:绝对底线,kwargs 不是什么神奇的东西。它只是一个保存键值对的字典。如果调用需要比提供的更多的位置参数,它可以查看关键字参数并接受一些值。但是,kwargs 不会调用一些魔法来关联所有作为 self.<some_name_here> 提供的名称。

因此,首先要直观地了解正在发生的事情,当您不理解一段代码时,您应该做的是确保它按照您认为的方式运行。让我们添加几个打印语句,看看发生了什么。

版本 1:

class Rectangle:
    def __init__(self, length, width, **kwargs):
        self.length = length
        self.width = width
        print(f"in rectangle. length = {self.length}, width = {self.width}")
    def area(self):
        return self.length * self.width


class Square(Rectangle):
    def __init__(self, length, **kwargs):
        print("in square")
        super().__init__(length = length, width = length, **kwargs)

class Triangle:
    def __init__(self, base, height, **kwargs):
        print("in triangle")
        self.base = base
        self.height = height
        super().__init__(**kwargs)
    def tri_area(self):
        return 0.5 * self.base * self.height

class Pyramid(Square, Triangle):
    def __init__(self, base, slant, **kwargs):
        print("in pyramid")
        kwargs["height"] = slant
        kwargs["length"] = base
        super().__init__(base = base, **kwargs)

    def surf_area(self):
        print(f"area : {super().area()}")
        print(f"tri_area : {super().tri_area()}")
        return super().area() + 4 * super().tri_area()

p = Pyramid(2,4)
p.surf_area()

输出:

in pyramid
in square
in rectangle. length = 2, width = 2
area : 4
#and then an error, note that it occurs when calling super().tri_area()
#traceback removed for brevity.
AttributeError: 'Pyramid' object has no attribute 'base'

我怀疑这已经打破了您对代码运行方式的一些假设。 请注意三角形的 init 从未被调用。但是让我们去掉工作正常的部分,并添加一个额外的打印语句。我也会冒昧地为第一个参数调用不同的值,这样更容易弹出。

版本 2:

class Rectangle:
    def __init__(self, length, width, **kwargs):
        self.length = length
        self.width = width
        print(f"in rectangle. length = {self.length}, width = {self.width}")
        print(f"kwargs are: {kwargs}")


class Square(Rectangle):
    def __init__(self, length, **kwargs):
        print("in square")
        super().__init__(length = length, width = length, **kwargs)

class Triangle:
    def __init__(self, base, height, **kwargs):
        print("in triangle")
        self.base = base
        self.height = height
        super().__init__(**kwargs)
    def tri_area(self):
        return 0.5 * self.base * self.height

class Pyramid(Square, Triangle):
    def __init__(self, base, slant, **kwargs):
        print("in pyramid")
        kwargs["height"] = slant
        kwargs["length"] = base
        super().__init__(base = base, **kwargs)

    def surf_area(self):
        print(f"tri_area : {super().tri_area()}")
        return super().tri_area()

p = Pyramid(10000,4)
p.surf_area()

输出:

in pyramid
in square
in rectangle. length = 10000, width = 10000
kwargs are: {'base': 10000, 'height': 4}
#error with traceback
AttributeError: 'Pyramid' object has no attribute 'base'

底线:kwargs 持有一个名为 base 的密钥,但这与 self.base 无关。但是,我的建议是摆脱整个 class 结构,花一些时间玩弄任何基本功能,摆脱多余的东西。

说,演示:

def some_func(a, b, **kwargs):
    print(a, b)
    print(kwargs)

some_func(1, 2)
some_func(1, 2, c=42)
some_func(a=1, c=42, b=2)

def other_func(a, b, **look_at_me):
    print(a, b)
    print(look_at_me)

other_func(1, 2)
other_func(1, 2, c=42)
other_func(a=1, c=42, b=2)

这两个块产生相同的输出。这里没有魔法。 输出:

1 2
{}
1 2
{'c': 42}
1 2
{'c': 42}

当您将 classes 添加到混合和继承中时,一次发生的事情太多了。更容易错过发生的事情,因此最好使用较小的代码示例。