Python 混入以扩展类属性

6 投票
1 回答
2395 浏览
提问于 2025-04-17 23:52

我正在尝试为Django的管理命令写一些混合类(mixins),目的是在不丢失当前类或任何继承类/混合类的值的情况下,包装BaseCommand.option_list。这样做的目的是为了避免在我的命令中写成BaseCommand.option_list + MyCommonOptionMixin.option_list + MyOtherCommonOptionMixin.option_list + (本地命令选项)

举个例子:

class BaseCommmand(object):
    option_list = (
        # Default options here.
    )

    # Rest of BaseCommand code. 

我定义了一个混合类,里面有一些常用的选项:

class MyCommonOptionMixin(object):
    option_list = (
        # Some common option/options I wish to have available in many commands
    )

    def __getattribute__(self, name):
        values = super(MyCommonOptionMixin, self).__getattribute__(name)
        if name == 'option_list':
            for option in self.option_list:
                if option not in values:
                    values += option,
        return values

也许我还有一个混合类,专门处理我有多个选项的情况。这两个混合类都重写了__getattribute__

class MyOtherCommonOptionMixin(object):
    option_list = (
        # Maybe I have another mixin I want to define separately
    )

    # Tried this, does not work with more than one mixin. 
    def __getattribute__(self, name):
        values = super(MyOtherCommonOptionMixin, self).__getattribute__(name)
        if name == 'option_list':
            for option in self.option_list:
                if option not in values:
                    values += option,
        return values

    # Works if the mixin defines the option_list under a different name, e.g. "_mymixin_options"
    # Then access at self._mymixin_options instead of self.option_list


class MyCommand(MyCommonOptionMixin, MyOtherCommonOptionMixin, BaseCommand):
    option_list = BaseCommand.option_list + (
        # Local defined options.
    )

我遇到了一个问题,就是如果混合类使用相同的名字来定义option_list属性,就会发生冲突。有没有比在混合类中给option_list起个独特的名字并重写__getattribute__更简单的方法来实现这个目标呢?

1 个回答

5

文档中提到的建议是,应该明确地把不同的选项列表连接起来。也就是说,如果你想这样做,我觉得使用一个自定义的元类是个不错的办法。可以像下面这样:

class CommandMetaclass(type):
    def __new__(mcl, name, bases, dct):
        # start with the option_list in the class we're creating
        # use set() to avoid duplicates
        option_list = set(dct.get('option_list', tuple()))

        # add the options from each base class
        for base in bases:
            option_list.update(base.__dict__.get('option_list', tuple()))

        # replace the original option_list with our combined version
        dct['option_list'] = tuple(option_list)

        return type.__new__(mcl, name, bases, dct)

class MyCommand(MyCommonOptionMixin, MyOtherCommonOptionMixin, BaseCommand):
    __metaclass__ = CommandMetaclass

    option_list = ( 
        # Local defined options.
    )

撰写回答