2010-01-01 6 views
2

У меня есть объект класса 'D' в python, и я хочу последовательно выполнить метод 'run', как определено 'D', и каждый из его предков ('A', ' B 'и' C ').Метод вызова для каждого предка в Python

Я могу сделать это, как этот

class A(object): 
    def run_all(self): 
     # I prefer to execute in revere MRO order 
     for cls in reversed(self.__class__.__mro__): 
      if hasattr(cls, 'run'): 
       # This works 
       cls.run(self) 
       # This doesn't 
       #cls.__getattribute__(self, 'run')() 

    def run(self): 
     print "Running A" 

class B(A): 
    def run(self): 
     print "Running B" 

class C(A): 
    def run(self): 
     print "Running C" 

class D(C, B): 
    def run(self): 
     print "Running D" 

if __name__ == "__main__": 
    D().run_all() 

что приводит к

$ python test.py 
Running A 
Running B 
Running C 
Running D 

Однако на практике я не знаю имя метода, который будет выполнен. Но если бы я попробовать это с помощью GetAttribute() (см комментируемого) линия не работает:

$ python test.py 
Running D 
Running D 
Running D 
Running D 

Так что мои вопросы:

  1. Почему это не работает?

  2. Это даже лучший способ сделать это?

ответ

1

Вы не должны использовать метод __getattribute__ ..

просто сделайте следующее:

getattr(cls, 'run')(self) 
+1

вы можете использовать __getattribute__, но он будет выглядеть очень «неплохо» - cls .__ getattribute __ (cls, 'run') (self) –

1

Почему вы не просто использовать super? Хотя некоторые consider it harmful, он был разработан с учетом такого рода сценария, и я бы использовал его без каких-либо колебаний.

От Python documentation:

This is useful for accessing inherited methods that have been overridden in a class. The search order is same as that used by getattr() except that the type itself is skipped. [...] This makes it possible to implement “diamond diagrams” where multiple base classes implement the same method.

Update: В вашем случае, это стало бы что-то вроде этого:

class A(object): 

    def run(self): 
     print "Running A" 

class B(A): 
    def run(self): 
     super(B, self).run() 
     print "Running B" 

class C(A): 
    def run(self): 
     super(C, self).run() 
     print "Running C" 

class D(C, B): 
    def run(self): 
     super(D, self).run() 
     print "Running D" 

if __name__ == "__main__": 
    D().run() 
+0

Я пробовал это изначально, но у меня были проблемы с супер (self .__ class__, self) (он всегда кажется, возвращает self .__ class__, по крайней мере, в ситуации выше), и я не хочу, чтобы пользователь каждый раз вызывал его. –

+0

Да, 'super' требует явной спецификации класса, так как в первом запросе кода моего ответа - использование' self .__ class__' в нем просто не может работать правильно. (Python 3 немного лучше, Кстати, кстати). –

+0

Почему вы не используете напрямую super (D, self) .run()? Я приводил пример в основном ответе. –

4

Если вы ОК с изменением всех run реализаций (и вызова run вместо run_all в D), это работает:

class A(object): 
    def run(self): 
     print "Running A" 

class B(A): 
    def run(self): 
     super(B, self).run() 
     print "Running B" 

class C(A): 
    def run(self): 
     super(C, self).run() 
     print "Running C" 

class D(C, B): 
    def run(self): 
     super(D, self).run() 
     print "Running D" 

if __name__ == "__main__": 
    D().run() 

Обратите внимание, что я делать не использовать super в корневом классе - это «знает», что нет никакого дальнейшего суперкласса идти до (object не определяет метод run). К сожалению, в Python 2 это неизбежно многословно (и не очень хорошо подходит для реализации через декоратора).

Ваш чек на hasattr довольно хрупкий, если я понимаю, ваши цели правильно - он найдет, что класс «имеет» атрибут, если он определяет или наследует его. Поэтому, если у вас есть промежуточный класс, который не переопределяет run, но имеет место на __mro__, то версия run, наследуемая, дважды вызывается в вашем подходе. Например.Рассмотрит:

class A(object): 
    def run_all(self): 
     for cls in reversed(self.__class__.__mro__): 
      if hasattr(cls, 'run'): 
       getattr(cls, 'run')(self) 
    def run(self): 
     print "Running A" 
class B(A): pass 
class C(A): 
    def run(self): 
     print "Running C" 
class D(C, B): pass 

if __name__ == "__main__": 
    D().run_all() 

это печатает

Running A 
Running A 
Running C 
Running C 

с двумя "заикается" для версий run этого B и D наследуют без переопределения (от A и C соответственно). Предполагая, что я прав, что это не эффекта, который вы хотите, если вы заинтересованы, чтобы избежать super вы можете попробовать изменить run_all к:

def run_all(self): 
    for cls in reversed(self.__class__.__mro__): 
     meth = cls.__dict__.get('run') 
     if meth is not None: meth(self) 

, который подставляется в мой последний пример только с двумя различными def s для run в A и C, делает пример печати:

Running A 
Running C 

, который я подозреваю, может быть ближе к тому, что вы хотите.

Еще одна боковая точка: не повторяйте работу - hasattr guarding getattr или in контрольно-проверочный доступ к диктофону - как проверка в охраннике, так и охраняемый аксессуар, должны повторять ту же самую работу внутри, к доброй цели. Скорее, используйте третий аргумент None одному звонку getattr (или методу get): это означает, что если этот метод отсутствует, вы получите значение None, а затем вы можете защитить звонок против этого вхождение. Именно поэтому dicts имеет метод get, а getattr имеет третий необязательный аргумент «по умолчанию»: чтобы упростить применение DRY, «не повторяйте себя», очень важный максимум хорошего программирования! -)

+0

Это именно то, что я искал, и вы совершенно правы в отношении «заиканий», я не думал об этом. Всем спасибо! –

+0

Кроме того, как я понимаю, чтобы использовать супер с множественным наследованием, вам нужно вызвать его из класса «root», чтобы пересечь все дерево/алмаз/как угодно (как это сделал Роберто ниже), хотя я мог ошибаться что. –

+0

А, я понимаю, что вы имеете в виду. Я стою исправлено. –

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