Python 3:super()意外引发TypeError

16

作为来自Java的开发者,我在学习Python中的继承、抽象类、静态方法以及其他面向对象编程概念时遇到了一些困难。

我已经实现了一个表达式树类,代码如下(简化版)

# Generic node class
class Node(ABC):
    @abstractmethod
    def to_expr(self):
        pass

    @staticmethod
    def bracket_complex(child):
        s = child.to_expr()
        return s if isinstance(child, Leaf) or isinstance(child, UnaryOpNode) else "(" + s + ")"


# Leaf class - used for values and variables
class Leaf(Node):
    def __init__(self, val):
        self.val = val

    def to_expr(self):
        return str(self.val)


# Unary operator node
class UnaryOpNode(Node):
    def __init__(self, op, child):
        self.op = op
        self.child = child

    def to_expr(self):
        return str(self.op) + super().bracket_complex(self.child)


# Binary operator node
class BinaryOpNode(Node):
    def __init__(self, op, lchild, rchild):
        self.op = op
        self.lchild = lchild
        self.rchild = rchild

    def to_expr(self):
        return super().bracket_complex(self.lchild) + " " + str(self.op) + " " + super().bracket_complex(self.rchild)


# Variadic operator node (arbitrary number of arguments)
# Assumes commutative operator
class VariadicOpNode(Node):
    def __init__(self, op, list_):
        self.op = op
        self.children = list_

    def to_expr(self):
        return (" " + str(self.op) + " ").join(super().bracket_complex(child) for child in self.children)

to_expr() 方法适用于 LeafUnaryOpNodeBinaryOpNode 的实例,但对于 VariadicOpNode 的实例调用该方法会引发 TypeError 异常:

TypeError: super(type, obj): obj must be an instance or subtype of type

在那门特定的课程中,我做错了什么导致super() 突然失效了?

在Java中,静态方法会被继承,所以我甚至不需要super调用,但在Python中似乎并非如此。


你正在使用Python2还是3?https://docs.python.org/2/library/functions.html#super - Jasper
@Jasper 因为他在没有参数的情况下使用了 super,所以我认为他正在使用 Python3.3+。 - Bakuriu
离题:为什么你在 to_expr 中使用 super() 调用 bracket_complex?你应该使用 self,否则你可能会在覆盖 bracket_complex 的子类中引入问题。 - Andrea Corbellini
正如标题所述,我正在使用Python 3。在Bakuriu下面的回答之后,我已经解决了super() -> self的问题。 - Mate de Vita
2个回答

13

您在生成器表达式中使用没有参数的 super()super() 是一种魔法 - 它依赖于调用帧中的信息。由于生成器表达式创建了一个额外的函数,因此没有参数的 super() 在那里无法工作。但是,由于您的超类在方法执行期间不太可能更改,所以可以将其移出生成器表达式 - 这也应该加快速度:

def to_expr(self):
    bracket_complex = super().bracket_complex
    return (" " + str(self.op) + " ").join(bracket_complex(child) for child in self.children)

然而,在Python中,由于静态方法是“继承”的,因此您可以通过 self 调用超类方法,前提是您没有在子类中覆盖它。因此,在这个简单的例子中,您可以编写:

def to_expr(self):
    return (" " + str(self.op) + " ").join(self.bracket_complex(child) for child in self.children)

具体实现细节是,如果不提供任何参数,则第一个参数应为调用者帧的__class__ cell中的值,第二个参数应为调用方函数给定的第一个参数。通常情况下,在错误的位置使用super()时会出现SystemError,但生成器表达式被包装在隐式生成器函数中,该函数创建另一个调用帧。不幸的是,这个函数得到了一个参数,这导致super()抛出此异常。

因此,通常情况下,super()将在那里作为第一个参数传递Foo,但在生成器表达式内部,传递了一个生成器对象-因此很明显需要引发TypeError



9

回答您隐含的问题:

在Java中,静态方法会被继承,因此我甚至不需要super调用,但在Python中似乎不是这种情况。

staticmethod是可以被继承的:

class A:
    @staticmethod
    def a():
        print('Hello')

class B(A):
    def b(self):
        self.a()

b = B()
b.a()
b.b()

输出:

Hello
Hello

请注意,你不能简单地写成以下内容:
class B(A):
    def b(self):
        a()

Python永远不会将一个简单的名称解析为方法/静态方法;对于Python,a()必须是一个函数调用,局部或全局。您必须使用self.a引用实例或使用B.a引用类。

在Python中,self是显式的,当前类引用也是如此。不要与Java的隐式this混淆。


啊,好的,那很有道理。 - Mate de Vita
在调试这个现象背后的原因时,我没能意识到 super() 是不必要的,所以点了个赞 :D - Antti Haapala -- Слава Україні

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接