Как обнаружить перегрузку метода в подклассах в 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

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

10 ответов

Решение

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

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

Если вы хотите проверить переопределенный метод экземпляра в Python 3, вы можете сделать это, используя тип self:

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

    def method(self):
        print('Hello from Base')


class Sub1(Base):
    def method(self):
        print('Hello from Sub1')


class Sub2(Base):
    pass

Теперь Base() и Sub2() должны печатать "одинаково", а Sub1() печатать "разные". Декоратор classmethod заставляет первый параметр быть привязанным к типу self, и поскольку тип подкласса по определению отличается от его базового класса, два метода класса будут сравниваться как не равные. Сделав метод методом экземпляра и используя тип self, вы сравниваете простую функцию с другой простой функцией и предполагаете, что функции (или несвязанные методы в этом случае, если вы используете Python 2) сравнивают себя равными (что они делают в реализации C Python), будет получено желаемое поведение.

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

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']
[]

В ответ на ответ /questions/5790660/kak-obnaruzhit-peregruzku-metoda-v-podklassah-v-python/5790671#5790671, поскольку у меня еще недостаточно кредитов, чтобы комментировать, он не будет работать в Python 3, если вы не замените im_func с __func__ и также не будет работать в Python 3.4(и, скорее всего, вперед), так как функции больше не имеют __func__ атрибут, только связанные методы.

РЕДАКТИРОВАТЬ: Вот решение исходного вопроса (который работал на 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

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

class A():
    def bla(self):
        print("Original")


class B(A):
    def bla(self):
        print("Overridden")

class C(A):
    pass


def isOverriddenFunc(func):
    obj = func.__self__
    prntM = getattr(super(type(obj), obj), func.__name__)

    return func.__func__ != prntM.__func__


b = B()
c = C()

b.bla()
c.bla()

print(isOverriddenFunc(b.bla))
print(isOverriddenFunc(c.bla))

Результат:

Overridden
Original
True
False

Конечно, чтобы это работало, метод должен быть определен в базовом классе.

Вы можете сравнить все, что есть в классе __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__) порядок разрешения метода (определяется __mro__ атрибут) и поиск дубликатов методов и атрибутов, определенных в каждом классе по цепочке наследования.

Вы также можете проверить, не переопределено ли что-то от его родителей, не зная ни одного из участвующих классов, используя super:

class A:
    def fuzz(self):
        pass

class B(A):
    def fuzz(self):
        super().fuzz()

class C(A):
    pass
>>> b = B(); c = C()
>>> b.__class__.fuzz is super(b.__class__, b).fuzz.__func__
False
>>> c.__class__.fuzz is super(c.__class__, c).fuzz.__func__
True

См. Этот вопрос для получения дополнительных сведений.

Общая функция:

def overrides(instance, function_name):
    return getattr(instance.__class__, function_name) is not getattr(super(instance.__class__, instance), function_name).__func__

>>> overrides(b, "fuzz")
True
>>> overrides(c, "fuzz")
False

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

class Test:
    def myfunc1(self):
        pass
    def myfunc2(self):
        pass

class TestSub(Test):
    def myfunc1(self):
        print('Hello World')

>>> test = TestSub()
>>> test.myfunc1.__func__ is Test.myfunc1
False
>>> test.myfunc2.__func__ is Test.myfunc2
True

Если дескриптор функции не указывает на функцию в суперклассе, значит, он был переопределен.

Не уверен, что это то, что вы ищете, но мне помогло, когда я искал подобное решение.

class A:
    def fuzz(self):
        pass

class B(A):
    def fuzz(self):
        super().fuzz()

assert 'super' in B.__dict__['fuzz'].__code__.co_names

Самый популярный ответ и несколько других используют ту или иную формуSub.method == Base.method. Однако это сравнение может вернуть ложноотрицательный результат, если и не используется один и тот же синтаксис импорта. Например, см. обсуждение здесь , объясняющее сценарий, в которомissubclass(Sub, Base) -> False.

Эта тонкость не очевидна при запуске многих минимальных примеров, но может проявиться в более сложной кодовой базе. Более надежным подходом является сравнение метода, определенного вSub.__bases__запись, соответствующаяBaseпотому что__bases__гарантированно использует тот же путь импорта, что иSub

      import inspect 

def method_overridden(cls, base, method):
   """Determine if class overriddes the implementation of specific base class method

   :param type cls: Subclass inheriting (and potentially overriding) the method
   :param type base: Base class where the method is inherited from
   :param str method: Name of the inherited method
   :return bool: Whether ``cls.method != base.method`` regardless of import
       syntax used to create the two classes
   :raises NameError: If ``base`` is not in the MRO of ``cls``
   :raises AttributeError: If ``base.method`` is undefined
   """

   # Figure out which base class from the MRO to compare against
   base_cls = None
   for parent in inspect.getmro(cls):
       if parent.__name__ == base.__name__:
           base_cls = parent
           break
   if base_cls is None:
       raise NameError(f'{base.__name__} is not in the MRO for {cls}')

   # Compare the method implementations
   return getattr(cls, method) != getattr(base_cls, method)
Другие вопросы по тегам