【问题标题】:How to detect method overloading in subclasses in python?如何检测python子类中的方法重载?
【发布时间】:2012-02-24 19:19:04
【问题描述】:

我有一个类是许多其他类的超类。我想知道(在我的超类的__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

有没有办法让超类知道子类是否覆盖了方法?

【问题讨论】:

  • 你能说几句为什么你想这样做吗?
  • 什么都不会立即出现,但是如果您在方法中包含文档字符串,那么当方法被覆盖时它们会被覆盖。因此,您应该能够使用MyClass.methodname.__doc__ 对其进行跟踪。但我发现这个解决方案非常hack,这就是为什么我没有将它作为答案发布
  • 基本上我想让超类将这些方法定义为“通过”,并有一个单独的方法(实际上是 init)调用这些函数。我想将 init 放入 print 语句中,说明我们正在启动或结束函数,但如果该函数仍然为空,这些语句看起来不合适,我想取消它们。
  • @Brian 为什么不让这些print 语句成为子类而不是超类方法的一部分?
  • 超类的部分目标是消除将这些常见项目添加到子类的需要。有一个超类和许多子类。

标签: python class overriding abstract-class


【解决方案1】:

如果你想在 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 实现中执行),将产生所需的行为。

【讨论】:

  • 这是最清晰的方法,type(self) 不需要 __func__ 的技巧。谢谢。
【解决方案2】:

通过比较实例的字典的公共子集和基类本身似乎最简单且足够了,例如:

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

【讨论】:

    【解决方案3】:

    您可以使用自己的装饰器。但这是一个技巧,仅适用于您控制实现的类。

    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
    

    【讨论】:

    • 我只是稍微不同的方向,用@native 装饰我的方法,并假设子类不会。我想我会这样做,因为我对子类没有太多控制权。效果很好,谢谢你的帮助!
    • 或者更简单的把Super.method._original = TrueSuper()类的body,然后检查是否hasattr(self.method, '_original')
    【解决方案4】:

    回复https://stackoverflow.com/a/9437273/1258307,由于我还没有足够的学分来评论它,它在python 3下不起作用,除非你用__func__替换im_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
    

    【讨论】:

    • 请注意:在 cython 中(至少在 python 3.4 中),self.method.__code__ 不存在,但可以在干净的 Python 中使用。由于某种原因,__code__dir(self.method) 中丢失了
    • self.method 是绑定方法,这意味着它不是实际方法。实际函数存储在__func__ 中。这里有更详细的描述 (docs.python.org/3/tutorial/classes.html#odds-and-ends)。
    • 您也可以使用type(self).method 并从那里访问__code__
    【解决方案5】:

    您可以通过查看函数句柄是否指向超类函数来检查函数是否已被覆盖。子类对象中的函数处理程序指向超类函数或子类中的重写函数。例如:

    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
    

    如果函数句柄不指向 Super 类中的函数,则它已被覆盖。

    【讨论】:

      【解决方案6】:

      您还可以使用 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
      

      有关更多信息,请参阅this question

      一般功能:

      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
      

      【讨论】:

      • 对不起,先生,我认为您在将代码转换为通用函数时翻转了输出。当我复制粘贴您的代码时,我得到相反的输出:overrides(b, "fuzz") 返回 False(应该是 True)和 overrides(c, "fuzz") 返回 True(应该是 False)
      • 在 python 2.7 中,通用函数应该是这样的:getattr(instance.__class__, function_name).__func__ is not getattr(super(instance.__class__, instance), function_name).__func__ 否则你正在比较两个不同的东西,它们总是不相等的
      • @pawel 我没有立即看到我的功能有什么不同?
      • @RobinDeSchepper 您在比较左侧缺少__func__
      • 但是左边的对象已经是函数的未绑定版本,它会抛出AttributeError: 'function' object has no attribute '__func__'。我没有 Python 2(现在没人应该了,它已经被它的制造者杀死了)所以我无法评估那里的情况。
      【解决方案7】:

      我正在使用以下方法来确定给定的绑定方法是被覆盖还是源自父类

      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
      

      当然,要使其工作,方法必须在基类中定义。

      【讨论】:

      • 一个非常优雅和简单的解决方案。
      【解决方案8】:

      您可以将类的 __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
      

      edit 更改的代码也可以与类方法一起正常工作: 如果它检测到父类上的类方法,则在继续之前提取底层函数。

      -- 另一种无需对类名进行硬编码的方法是遵循实例的类 (self.__class__) 方法解析顺序(由 __mro__ 属性给出)并搜索在继承链中的每个类。

      【讨论】:

      • 在 Python 3 中它是 __func__ 属性。
      【解决方案9】:

      不确定这是否是您正在寻找的,但当我在寻找类似的解决方案时它帮助了我。

      class A:
          def fuzz(self):
              pass
      
      class B(A):
          def fuzz(self):
              super().fuzz()
      
      assert 'super' in B.__dict__['fuzz'].__code__.co_names
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-08-04
        • 1970-01-01
        • 2019-02-08
        • 2016-03-11
        • 1970-01-01
        • 2018-04-24
        • 2010-12-05
        • 2010-12-16
        相关资源
        最近更新 更多