2014-12-24 3 views
0

Определенные классы в стандартной библиотеке Python (и в более общем плане) используют динамическую отправку для вызова специализированных методов в подклассах.Как вызвать супер, если родительский метод не может быть определен?

Например, класс ast.NodeVisitor определяет метод visit. Этот метод вызывает, при необходимости, методы visit_classname. Эти методы не определены непосредственно на ast.NodeVisitor, но могут предоставляться заинтересованными подклассами.

Другими словами, подклассов переопределить только те методы, которые они хотели бы обрабатывать, например:

class SpecialNodeVisitor(ast.NodeVisitor): 
    def visit_FunctionDef(self, node): 
     print(node) # prints any node of type FunctionDef 

Вещи становятся более сложными, если SpecialNodeVisitor сама подклассы. super() может быть использован, если visit_FunctionDef будет перекрываться, но не в других случаях, а именно:

class EvenMoreSpecialNodeVisitor(SpecialNodeVisitor): 
    def visit_FunctionDef(self, node): 
     super().visit_FunctionDef(node) # works fine 
     # ... 

    def visit_Call(self, node): 
     super().visit_Call(node) # AttributeError 
     # ... 

В частности, во втором примере вызывает AttributeError: 'super' object has no attribute 'visit_Call'.


Такое поведение имеет смысл: родительский класс не есть метод в вопросе. Однако, это вызывает две проблемы:

  • При написании подкласса, некоторые динамические методы нужно вызвать super(), но некоторые этого не делают. Это несоответствие делает его действительно легко ошибиться.
  • Если новый динамический метод позже добавляется к родительскому классу, все подклассы должны быть изменены для вызова super(). Это нарушает действительно фундаментальное правило объектно-ориентированного программирования.

В идеале, все методы подкласса должны быть в состоянии использовать в super(), с вызов не будучи не-оп, если метод не определен. Есть ли «питонический» способ достичь этого?

Я особенно после решения, которое является прозрачным для подкласса (например, я не хочу пытаться/кроме AttributeError в каждом отдельном методе, так как это было бы так же легко забыть и уродливо как ад).

(Стоит отметить, что во многих случаях, и на самом деле в данном конкретном примере, это не возможно, чтобы просто определить все возможные методы родительского класса, так как это может иметь побочные эффекты.)

+0

A) добавить методы no-op к родительскому классу B) инкапсулировать подход try-except. –

+0

Что вы здесь описываете, это просто нормальное поведение языка. Выглядит очень логично для меня, если в одном из родительских классов нет метода для повышения ошибки атрибута super(). Как вы можете называть то, чего не существует ?! Если вы действительно хотите это сделать, добавьте метод no-op в родительские классы. – andrefsp

+0

Чувак, добавление методов в классы во время выполнения просто неверно. Почему ты бы так поступил? Это порождает такие проблемы. Я уверен, этого можно избежать. Возможно, попробуйте перепроектировать ваше приложение. – freakish

ответ

1

Вы не можете есть то, что вы хотите; наиболее читаемый метод просто использовать try..except на что AttributeError:

def visit_Call(self, node): 
    try: 
     super().visit_Call(node) 
    except AttributeError: 
     pass 

Альтернативой было бы для вас, чтобы добавить псевдонимы для NodeVisitor.generic_visit для каждого типа узла к SpecialNodeVisitor:

import inspect 

class SpecialNodeVisitor(ast.NodeVisitor):  
    def visit_FunctionDef(self, node): 
     print(node) # prints any node of type FunctionDef 

_ast_nodes = inspect.getmembers(
    ast, 
    lambda t: isinstance(t, type) and issubclass(t, ast.AST) and t is not ast.AST) 
for name, node in _ast_nodes: 
    name = 'visit_' + name 
    if not hasattr(SpecialNodeVisitor, name): 
     setattr(SpecialNodeVisitor, name, ast.NodeVisitor.generic_visit) 

Вы можете инкапсулировать, что в мета-класс, если вы хотите.Так как super() смотрит прямо в пространство классов __dict__, вы не можете просто определить метод __getattr__ для метакласса для динамического поиска, к сожалению.

+0

Это на самом деле просто великолепно. Я подумал об использовании 'inspect', чтобы получить все возможные методы, но мне даже не приходило в голову, чтобы они называли их« generic_visit »; Благодаря! – sapi

Смежные вопросы