How to detect method overloading in subclasses in

2019-02-05 20:44发布

I have a class that is a super-class to many other classes. I would like to know (in the init() of my super-class if the subclass has overridden a specific method.

I tried to accomplish this with a class method, but the results were wrong:

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

Is there any way for a super-class to know if a sub-class has overridden a method?

5条回答
混吃等死
2楼-- · 2019-02-05 21:29

You can compare whatever is in the class's __dict__ with the function inside the method you can retrieve from the object - the "detect_overriden" functionbellow does that - the trick is to pass the "parent class" for its name, just as one does in a call to "super" - else it is not easy to retrieve attributes from the parentclass itself instead of those of the subclass:

# -*- 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

edit changed code to work fine with classmethods as well: if it detects a classmethod on the parent class, extracts the underlying function before proceeding.

-- Another way of doing this, without having to hard code the class name, would be to follow the instance's class ( self.__class__) method resolution order (given by the __mro__ attribute) and search for duplicates of the methods and attributes defined in each class along the inheritance chain.

查看更多
Emotional °昔
3楼-- · 2019-02-05 21:35

You can use your own decorator. But this is a trick and will only work on classes where you control the implementation.

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
查看更多
叛逆
4楼-- · 2019-02-05 21:41

In reply to answer https://stackoverflow.com/a/9437273/1258307, since I don't have enough credits yet to comment on it, it will not work under python 3 unless you replace im_func with __func__ and will also not work in python 3.4(and most likely onward) since functions no longer have the __func__ attribute, only bound methods.

EDIT: Here's the solution to the original question(which worked on 2.7 and 3.4, and I assume all other version in between):

    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

And here's the output:

same
different
same
查看更多
SAY GOODBYE
5楼-- · 2019-02-05 21:42

Not sure if this is what you're looking for but it helped me when I was looking for a similar solution.

class A:
    def fuzz(self):
        pass

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

assert 'super' in B.__dict__['fuzz'].__code__.co_names
查看更多
等我变得足够好
6楼-- · 2019-02-05 21:50

It seems simplest and sufficient to do this by comparing the common subset of the dictionaries of an instance and the base class itself, e.g.:

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()

Runs and gives:

['method1']
[]
查看更多
登录 后发表回答