如何在Python中对多个类通用地应用函数重写?

5 投票
2 回答
1024 浏览
提问于 2025-04-16 17:55

我正在做一个Django应用,但这看起来更像是一个Python的问题,并不特别针对Django。我对Python还很陌生,想要描述我想做的事情有点难,但展示出来就简单多了,所以我来试试:

我有一个类:

class SlideForm(ModelForm):

    class Meta:
        model = Slide

然后我从这个类继承了一个子类:

class HiddenSlideForm(SlideForm):
    def __init__(self, *args, **kwargs):
        super(HiddenSlideForm, self).__init__(*args, **kwargs)
        for name, field in self.fields.iteritems():
            field.widget = field.hidden_widget()
            field.required = False

接着我还有另一个类:

class DeckForm(ModelForm):     

    def __init__(self, *args, **kwargs):
        # do some stuff here
        return super(DeckForm, self).__init__(*args, **kwargs)

    class Meta:
        model = Deck
        # other stuff here  

我也从这个类继承了一个子类:

class HiddenDeckForm(DeckForm):

    def __init__(self, *args, **kwargs):
        super(HiddenDeckForm, self).__init__(*args, **kwargs)
        for name, field in self.fields.iteritems():
            field.widget = field.hidden_widget()
            field.required = False

注意,这些子类除了类名不同,代码完全一样,功能也完全相同。我一直在想怎么才能把这个过程变得更通用,这样我就可以保持代码的简洁性(DRY原则),并且能方便地用于其他类。我考虑过使用装饰器或者多重继承——这两个概念对我来说都是新的——但我总是搞不清楚。

如果有人能帮忙,我会很感激!

(顺便说一下,欢迎指出我Django代码中的任何问题 :))

2 个回答

0

多重继承(特别是混入类)可能是这里最好的解决方案。

举个例子:

class HiddenFormMixin(object):
    def __init__(self, *args, **kwargs):
        for name, field in self.fields.iteritems():
            field.widget = field.hidden_widget()
            field.required = False


class SlideForm(ModelForm):
    class Meta:
        model = Slide


class HiddenSlideForm(SlideForm, HiddenFormMixin):
    pass


class DeckForm(ModelForm):     
    def __init__(self, *args, **kwargs):
        # do some stuff here
        return super(DeckForm, self).__init__(*args, **kwargs)

    class Meta:
        model = Deck
        # other stuff here  


class HiddenDeckForm(DeckForm, HiddenFormMixin):
    pass

请注意,如果你在两个类中都重写了__init__方法,这可能不会直接奏效。在这种情况下,你可以像这样指定顺序:

class HiddenDeckForm(DeckForm, HiddenFormMixin):
    def __init__(self, *args, **kwargs):
        # do some stuff here
        DeckForm.__init__(self, *args, **kwargs)
        HiddenFormMixin.__init__(self, *args, **kwargs)
4

一种选择是使用混入类(Mixin class);举个例子:

首先,把共同的行为放在混入类里:

class SomeMixin(object):
    def __init__(self, *args, **kwargs):
        super(SomeMixin, self).__init__(*args, **kwargs)
        for name, field in self.fields.iteritems():
            field.widget = field.hidden_widget()
            field.required = False

只要你能合理控制所有的继承类,并且在每个需要重写的方法里都调用了 super,那么派生类的样子就没那么重要了。

但是,当某个父类没有在正确的时机调用 super 时,就会出现问题。在这种情况下,重写的方法必须被最后调用,因为一旦调用了,就不会再有其他调用了。

最简单的解决办法是确保每个类都实际继承了那个有问题的父类,但有时候这根本不可能;创建一个新类会生成一个你其实不想要的对象!另一个原因可能是逻辑上的基类在继承树上太高,难以处理。

在这种情况下,你需要特别注意基类的 顺序。Python会优先考虑最左边的父类,除非在继承图中有更派生的类。这是个复杂的话题,要理解Python到底在干什么,你应该了解一下 C3 MRO算法,这个算法在Python 2.3及以后的版本中都有。

基类和之前一样,但由于所有的共同代码都来自混入类,派生类变得很简单。

class HiddenSlideForm(SomeMixin, SlideForm):
    pass

class HiddenDeckForm(SomeMixin, DeckForm):
    pass

注意,混入类出现在 最前面,因为我们无法控制 *Form 类在它们的初始化方法中做了什么。

如果其中任何一个的 __init__ 方法比较复杂,你仍然会有收获。

class HiddenSlideForm(SomeMixin, SlideForm):
    def __init__(self, *args, **kwargs):
        super(HiddenSlideForm, self).__init__(*args, **kwargs)
        do_something_special()

确保 object 在继承图中某个地方存在。否则可能会发生奇怪的事情。

撰写回答