【问题标题】:Is it possible to detect conflicting method names in Python?是否可以在 Python 中检测冲突的方法名称?
【发布时间】:2013-03-10 22:37:09
【问题描述】:

我创建了一个名为Liger 的Python 类,它扩展了一个名为Lion 的类和一个名为Tiger 的类。 Liger 类从 LionTiger 继承了 speak() 方法,但语法仍然有效 - 不打印错误消息,但 Tigerspeak() 方法的实现是由Liger 继承。是否可以在 Python 中检测到类似这样的方法名冲突,以便在这种方式的方法名冲突时打印错误消息?

'''
Conflicting method names in python
'''


class Tiger():
    @staticmethod
    def speak():
        print "Rawr!";

class Lion():
    @staticmethod
    def speak():
        print "Roar!";

class Liger(Tiger, Lion):
    pass
'''both superclasses define a speak() method, and I need a way to detect this type of conflict.'''    

Liger.speak(); ''' this prints "Rawr" instead of printing an error message. '''
'''Is there any way to detect method name collisions like this one?'''

代码可以在这里在线测试和调试:http://ideone.com/xXOoVq

【问题讨论】:

  • 不使用多重继承来解决问题?
  • @hughdbrown 这是一个关于 Python 中多重继承的问题,我认为多重继承在某些情况下非常有用(例如,使用类来模拟物种之间的生物学关系,其中不同亚种之间的杂交发生,所有物种都有一个共同的祖先)。我宁愿找到解决这个问题的方法,也不愿完全避免解决这个问题。
  • 如果我正在创建某种史诗般的奇幻游戏,我可能会有这样一个场景,其中 Dragon 类继承自 Lizard 和 Reptile 类,其中 Lizard 和 Reptile 都继承自 Amphibian 类,其中类Amphibian 继承自 Fish 类。在这个(非常有趣的)场景中,多重继承绝对是必不可少的。
  • 专业/现代游戏开发使用entity-component systems 而不是基于类的继承(当然不是多重继承)系统准确来说是因为这样的问题。是的,多重继承有其用途,但建模游戏实体不是其中之一!

标签: python inheritance multiple-inheritance python-2.x


【解决方案1】:

我不确定继承是否是您用来解决问题的正确机制。继承通常被称为定义两个类之间的“IS-A”关系。也就是说,如果A 继承自B,则B 的每个实例也是A 的一个实例。

如果您使用多重继承,则该类最终会同时成为多种对象。在您的示例中,Liger 实例同时是 TigerLion

您可能不想使用类继承来模拟物种继承。尽管有名字,但它们的含义并不相同。例如,家猫可能是某种老虎的后代,但说家猫是老虎是不正确的。

您在类上使用静态方法这一事实让我认为您可能根本不想使用类,而是更通用的 Animal 类的实例:

class Animal(object):
    def __init__(self, sound):
        self.sound = sound

    def speak(self):
        print self.sound

lion = Animal("Roar!")

那里没有继承机制,但可以添加一种方法以某种方式将一种动物变异为另一种动物。

现在,如果您真的打算使用类和多重继承,有两种不错的方法(使用常规实例方法,而不是静态方法):

首先是做“协作多重继承”,让你的每个方法在 MRO 中的下一个类上调用相同的方法。通常你需要继承一个通用的基类来完成这项工作(否则最后一次调用super 将得到object,它不会定义方法):

class Animal(object):
    def speak(self):
        pass

class Lion(Animal):
    def speak(self):
        print("Roar!")
        super(Lion, self).speak()

class Tiger(Animal):
    def speak(self):
        print("Rawr!")
        super(Tiger, self).speak()

class Liger(Lion, Tiger):
    pass

在这种情况下,Liger 实例的speak 方法将同时打印RoarRawr,这是有道理的,因为它同时是狮子和老虎。不过要小心这种编码风格,因为要让协作正常工作可能会很棘手。例如,您不能更改方法签名并期望它在协作多重继承情况下正常工作(除非您仅使用关键字参数,并在 **kwargs 中传递任何无法识别的参数)。

第二种解决方案是给Liger 它自己的speak 实现,该实现明确地选择要做什么(可能是调用它的基类之一)。这在其他一些语言中是必需的,例如 C++。如果您请求的继承没有做您想要的,这可能是最直接的解决方法:

class Liger(Lion, Tiger):
    def speak(self):
        Tiger.speak(self)

【讨论】:

  • 这些解决方案如何更容易检测单个类的超类中的方法冲突? (我想第一个解决方案可能比第二个解决方案更有用,因为两个超类的方法将合并为一个,使方法冲突更加明显。)
  • 另外,在我原来的例子中,所有类的方法都是静态方法。在这个答案中,没有一个方法是静态方法。
  • @AndersonGreen:简而言之,我认为在方法冲突中检测并引发错误是在解决错误的问题。如果多重继承完全是正确的解决方案,您希望能够有冲突的方法(使用覆盖或超级调用以从中获得正确的结果)。至于使用静态方法,这似乎是一个非常奇怪的选择(“老虎”这个物种不咆哮,个别老虎会咆哮)。我将编辑我的答案,以更明确地说明我在回答什么。
【解决方案2】:

您可以使用元类检测这种冲突:

class ConflictCheck(type):
    def __new__(meta, name, bases, dct):
        # determine attributes per base class, except for magic ones
        attrs_per_base = [set(a for a in dir(b) if not a.startswith("__"))
                          for b in bases]
        if len(set.union(*attrs_per_base)) < sum(map(len, attrs_per_base)):
            raise ValueError("attribute conflict")
        return super(ConflictCheck, meta).__new__(meta, name, bases, dct)

class Liger(Lion, Tiger):
    __metaclass__ = ConflictCheck  # will raise an error at definition time

这是一个非常粗略的第一个版本,错误消息很差,只要方法在继承树中的 Liger 以上被覆盖,它实际上就会引发错误,但它应该足以让您入门。

【讨论】:

  • 如果您在继承树的任何位置覆盖任何方法,这不会引发错误吗?
  • @Blckknght:是的,或者至少在继承树中Liger 上方的任何地方,正如我在代码下方指出的那样。如果你知道如何获得直接超类,请告诉我。
  • 这里在ideone上,方便在线测试调试:ideone.com/bZMSwt
  • @larsmans:我认为你实际上比我最初想象的更接近。 __bases__ 确实给出了直接的超类,所以并不是所有的覆盖都会破坏它,就像我最初想的那样。但是,dir 给出的值是从树的更上层继承而来的,所以如果基类有一个具有非特殊方法的共同祖先,就会产生冲突。
  • @Blckknght:b.__dict__ 会更好吗? (我不会每天都定义元类...)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-03-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多