вызовите метод Python, если он существует - PullRequest
0 голосов
/ 05 июня 2018

Как вызвать метод подкласса из метода базового класса, только если подкласс поддерживает этот метод?И какой лучший способ сделать это?В качестве наглядного примера у меня есть животное, которое защищает мой дом: если кто-то пройдет мимо, он будет выглядеть злым, и он будет лаять, если сможет.

Пример кода:

class Protector(object):
    def protect(self):
        self.lookangry()
        if hasattr(self, 'bark'):
            self.bark()

class GermanShepherd(Protector):
    def lookangry(self):
        print u') _ _ __/°°¬'
    def bark(self):
        print 'wau wau'

class ScaryCat(Protector):
    def lookangry(self):
        print '=^..^='

Я могу подуматьиз множества альтернативных реализаций для этого:

  1. Использование hasattr, как указано выше.
  2. try: self.bark() except AttributeError: pass, но это также перехватывает любые ошибки AttributeErrors в bark
  3. То жекак 2, но проверьте сообщение об ошибке, чтобы убедиться, что это правильный AttributeError
  4. Как 2, но определите метод абстрактной коры, который поднимает NotImplementedError в абстрактном классе и проверяет NotImplementedError вместо AttributeError.При таком решении Pylint будет жаловаться, что я забыл переопределить абстрактный метод в ScaryCat.
  5. Определить пустой метод коры в абстрактном классе:

    class Protector(object):
        def protect(self):
            self.lookangry()
            self.bark()
        def bark(self):
            pass
    

Я полагал, что в Python они обычно должны быть одним из способов что-то сделать.В этом случае мне не ясно, какой.Какой из этих вариантов наиболее удобен для чтения, наименее вероятно, что он может вызвать ошибку, когда материал меняется и наиболее соответствует стандартам кодирования, особенно Pylint?Есть ли лучший способ сделать это, что я пропустил?

Ответы [ 3 ]

0 голосов
/ 05 июня 2018

Мне кажется, вы неправильно думаете о наследовании.Предполагается, что базовый класс инкапсулирует все, что является общим для любого из подклассов.Если что-то не является общим для всех подклассов, по определению оно не является частью базового класса.

Так что ваше утверждение «если кто-то пройдет мимо, будет выглядеть злым, и оно будет лаять, если может», неимеет смысл для меня.Часть «Кора, если она может» не является общей для всех подклассов, поэтому она не должна быть реализована в базовом классе.

Что должно произойти, так это то, что подкласс, который вы хотитекора добавляет эту функциональность к методу protect().Как в:

class Protector():
    def protect(self):
        self.lookangry()

class GermanShepherd(Protector):
    def protect(self):
        super().protect() # or super(GermanShepherd, self).protect() for Python 2
        self.bark()

Таким образом, все подклассы будут lookangry(), но подклассы, которые реализуют метод bark(), будут иметь его как часть расширенной функциональности метода protect() суперкласса.

0 голосов
/ 05 июня 2018

Я думаю, что 6.) может быть, класс Protector делает абстрактными только основные разделяемые методы, оставляя дополнительные методы своим наследникам.Конечно, это можно разделить на несколько подклассов, см. https://repl.it/repls/AridScrawnyCoderesource (Написано на Python 3.6)

class Protector(object):
  def lookangry(self):
    raise NotImplementedError("If it can't look angry, it can't protect")

  def protect(self):
      self.lookangry()


class Doggo(Protector):
  def bark(self):
    raise NotImplementedError("If a dog can't bark, it can't protect")

  def protect(self):
    super().protect()
    self.bark()


class GermanShepherd(Doggo):

  def lookangry(self):
    print(') _ _ __/°°¬')

  def bark(self):
    print('wau wau')


class Pug(Doggo):
  # We will not consider that screeching as barking so no bark method
  def lookangry(self):
    print('(◉ω◉)')


class ScaryCat(Protector):
  def lookangry(self):
      print('o(≧o≦)o')


class Kitten(Protector):
  pass


doggo = GermanShepherd()
doggo.protect()

try:
  gleam_of_silver = Pug()
  gleam_of_silver.protect()
except NotImplementedError as e:
  print(e)

cheezburger = ScaryCat()
cheezburger.protect()

try:
  ball_of_wool = Kitten()
  ball_of_wool.protect()
except NotImplementedError as e:
  print(e)
0 голосов
/ 05 июня 2018

Вы упустили одну возможность:

Определите метод bark, который вызывает NotImplementedError, как в вашем варианте 4, но не делает его абстрактным.

Это исключает жалобу PyLint и, что более важно, устраняет законную проблему, на которую он жаловался.


Что касается других ваших вариантов:

  • hasattr не требуетсяLBYL, который обычно не является Pythonic.
  • Проблему except можно решить, выполнив bark = self.bark внутри блока try, затем выполнив bark(), если он пройдет.Иногда это необходимо, но тот факт, что это немного неуклюже и не было «исправлено», должен дать вам представление о том, как часто это стоит делать.
  • Проверка сообщений об ошибках является антишаблоном.Все, что не является отдельным документированным значением аргумента, может быть изменено во всех версиях и реализациях Python.(Плюс, что если ManWithSidekick.bark() делает self.sidekick.bark()? Как бы вы различали AttributeError там?)

Итак, это оставляет 2, 4.5 и 5.

Я думаю, что в большинстве случаев, 4.5 или 5 будет правильным решением.Разница между ними не прагматичная, а концептуальная: если животное ScaryCat лает молча, используйте опцию 5;если нет, то лай должен быть необязательной частью защиты, которую делают не все защитники, и в этом случае используйте опцию 4.5.

Для этого игрушечного примера, я думаю, я бы использовал опцию 4.5.И я думаю, что так будет с большинством игрушечных примеров, которые вы придумали.

Однако я подозреваю, что большинство реальных примеров будут довольно разными:

  • Большинству реальных примеров не понадобится эта глубокая иерархия.
  • Ofте, которые это делают, обычно либо bark, либо будут реализованы всеми подклассами, либо не будут вызваны суперклассом.
  • Из тех, кому это нужно, я думаю, вариант 5 подойдет.Конечно, bark в молчании это не то, что делает ScaryCat, но parse_frame в молчании это то, что делает ProxyProtocol.
  • И после этого осталось так мало исключений, что трудно говоритьо них абстрактно и вообще.
...