Python 3: super() неожиданно вызывает TypeError

Начиная с Java, я немного борюсь с наследованием, абстрактными классами, статическими методами и схожими понятиями программирования OO в 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() отлично работает при вызове экземпляров Leaf, UnaryOpNode и BinaryOpNode, но вызывает TypeError при вызове экземпляра VariadicOpNode:

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

Что я делаю неправильно в этом конкретном классе, который super() внезапно не работает?

В Java статический метод получил бы унаследованный, поэтому мне даже не понадобился бы супервызов, но в Python это, похоже, не так.

Ответы

Ответ 1

Вы используете 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__ кадра вызывающего абонента, а второе должно быть первым аргументом, заданным для функции вызывающего абонента. Обычно вы просто получаете SystemError при использовании super в неправильном месте, но выражения генератора обернуты внутри неявной функции генератора, которая создает еще один кадр вызова. К сожалению, эта функция получает аргумент, который вызывает super(), чтобы жаловаться на это исключение.

Таким образом, обычно super() передается Foo там как первый аргумент, но в выражении генератора передается объект-генератор - и, следовательно, очевидно, что нужно TypeError.

Ответ 2

Отвечая на ваш предполагаемый вопрос:

В Java статический метод будет наследоваться, поэтому мне даже не понадобится супервызов, но в 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 будет никогда разрешать простое имя методу /staticmethod; для Python a() должен быть вызов функции, локальный или глобальный. Вы должны либо ссылаться на экземпляр, используя self.a, либо класс, используя B.a.

В python self является явным, как и текущая ссылка класса. Не путайте с неявным this Java.