Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python mixin to extend class property

Trying to figure out how to write some mixins for Django management command that will wrap the BaseCommand.option_list without losing the value of the current class or any inherited classes/mixins. The goal is to avoid doing BaseCommand.option_list + MyCommonOptionMixin.option_list + MyOtherCommonOptionMixin.option_list + ( local command options ) in my commands.

Example:

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

    # Rest of BaseCommand code. 

I define a mixin with some common options:

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

Maybe I have one more, just to cover that case where I have multiple. The mixins both override __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.
    )

I've run into collision if the mixins use the same name for the option_list property. Is there a cleaner way to achieve this goal than naming the option_list uniquely inside the mixins and overriding __getattribute__?

like image 797
kevins Avatar asked Mar 25 '14 19:03

kevins


1 Answers

The advice in the documentation is to explicitly concatenate the various option lists. That said, if you want to go this route I think a custom metaclass is the right approach. Something like:

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.
    )
like image 112
Kevin Christopher Henry Avatar answered Oct 26 '22 14:10

Kevin Christopher Henry