Как обнаружить перегрузку метода в подклассах в python?

У меня есть класс, который является суперклассом для многих других классов. Я хотел бы знать (в init() моего суперкласса, если подкласс переопределил конкретный метод.

Я попытался выполнить это с помощью метода класса, но результаты были неправильными:

class Super:
   def __init__(self):
      if self.method == Super.method:
         print 'same'
      else:
         print 'different'

   @classmethod
   def method(cls):
      pass

class Sub1(Super):
   def method(self):
      print 'hi'

class Sub2(Super):
   pass

Super() # should be same
Sub1() # should be different
Sub2() # should be same

>>> same
>>> different
>>> different

Есть ли способ суперкласса узнать, переопределил ли подкласс класс?

Ответ 1

Вы можете использовать свой собственный декоратор. Но это трюк и будет работать только на классах, где вы контролируете реализацию.

def override(method):
  method.is_overridden = True
  return method

class Super:
   def __init__(self):
      if hasattr(self.method, 'is_overridden'):
         print 'different'
      else:
         print 'same'
   @classmethod
   def method(cls):
      pass

class Sub1(Super):
   @override
   def method(self):
      print 'hi'

class Sub2(Super):
   pass

Super() # should be same
Sub1() # should be different
Sub2() # should be same

>>> same
>>> different
>>> same

Ответ 2

В ответ на fooobar.com/questions/4703343/..., так как у меня пока нет комментариев, чтобы прокомментировать это, он не будет работать под python 3, если вы не замените im_func на __func__ и также не сможете работать в python 3.4 (и, скорее всего, дальше), поскольку функции больше не имеют атрибута __func__, только связанные методы.

EDIT: Здесь решение первоначального вопроса (который работал с 2,7 и 3,4, и я предполагаю, что все остальные версии находятся между ними):

    class Super:
        def __init__(self):
            if self.method.__code__ is Super.method.__code__:
                print('same')
            else:
                print('different')

        @classmethod
        def method(cls):
            pass

    class Sub1(Super):
        def method(self):
            print('hi')

    class Sub2(Super):
        pass

    Super() # should be same
    Sub1() # should be different
    Sub2() # should be same

И вот вывод:

same
different
same

Ответ 3

Проще всего и достаточно сделать это, сравнивая общее подмножество словарей экземпляра и самого базового класса, например:

def detect_overridden(cls, obj):
  common = cls.__dict__.keys() & obj.__class__.__dict__.keys()
  diff = [m for m in common if cls.__dict__[m] != obj.__class__.__dict__[m]]
  print(diff)

def f1(self):
  pass

class Foo:
  def __init__(self):
    detect_overridden(Foo, self)
  def method1(self):
    print("Hello foo")
  method2=f1

class Bar(Foo):
  def method1(self):
    print("Hello bar")
  method2=f1 # This is pointless but not an override
#  def method2(self):
#    pass

b=Bar()
f=Foo()

Запускает и дает:

['method1']
[]

Ответ 4

Вы можете сравнить все, что находится в классе __dict__, с функцией внутри метода, которую вы можете извлечь из объекта. Функция "detect_overriden" выполняет это: трюк состоит в том, чтобы передать "родительский класс" для своего имени, как это делается в вызов "super" - иначе получить атрибуты из самого родительского класса, а не из подкласса, нелегко:

# -*- coding: utf-8 -*-
from types import FunctionType

def detect_overriden(cls, obj):
    res = []
    for key, value in cls.__dict__.items():
        if isinstance(value, classmethod):
            value = getattr(cls, key).im_func
        if isinstance(value, (FunctionType, classmethod)):
            meth = getattr(obj, key)
            if not meth.im_func is  value:
                res.append(key)
    return res


# Test and example
class A(object):
    def  __init__(self):
        print detect_overriden(A, self)

    def a(self): pass
    @classmethod
    def b(self): pass
    def c(self): pass

class B(A):
    def a(self): pass
    #@classmethod
    def b(self): pass

отредактируйте измененный код, чтобы нормально работать с классами: если он обнаруживает метод класса в родительском классе, извлекает основную функцию перед продолжением.

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