Управление несколькими миксинами - лучше с метаклассом? - PullRequest
0 голосов
/ 12 июня 2018

У меня есть набор миксинов, которые можно динамически добавлять в базовый класс, смешивать и сопоставлять.Все они имеют одинаковые свойства и методы, но не должны перекрывать друг друга, когда объединяются несколько миксов.Лучше ли построить обычный класс MixinManager для обработки вещей или MetaClass, чтобы изменить поведение моего основного BaseClass в зависимости от того, какие миксины объединены вместе?

Например, все мои миксины переопределяют метод _full.Тем не менее, я хочу, чтобы один full метод вызывал какой-либо метод _full, смешанный с моим базовым классом.

классы mixin

Эти миксины имеют общие методы и свойства.Я хотел бы избежать переопределения всех базовых методов в каждом подклассе mixin.Это много повторяющегося кода.

import six
import abc
import inspect
import sys

class BaseMixin(six.with_metaclass(abc.ABCMeta, object)):

    @abc.abstractmethod
    def _full(self):
        pass

    # not working yet
    def has_name(self):
        return self._name is not None


class AMixin(BaseMixin):

    def __init__(self, db_name=None, **kwargs):
        super(AMixin, self).__init__(**kwargs)
        self.db_name = db_name

    def _full(self):
        print('i am an A mixin', self.db_name)


class BMixin(BaseMixin):

    def __init__(self, ext_name=None, **kwargs):
        super(BMixin, self).__init__(**kwargs)
        self.ext_name = ext_name

    def _full(self):
        print('i am a B mixin', self.ext_name)


MIXINS = {name.lower().split('mixin')[0]: obj for name, obj in inspect.getmembers(sys.modules[__name__], inspect.isclass)
          if 'Mixin' in name and 'Base' not in name}

класс mixin manager

Я могу заставить его работать с обычной системой менеджера классов.Однако мне нужно будет установить множество методов и свойств «менеджера», и они, похоже, станут громоздкими.Я не хочу жестко определять каждый метод / свойство, для которого мне нужно это сделать.

class MixinManager(object):

    def __new__(cls, *args, **kwargs):

        mixins = [obj for obj in cls.__bases__ if 'Mixin' in obj.__name__]
        if len(mixins) == 1:
            setattr(cls, 'full', mixins[0]._full)

        return super(MixinManager, cls).__new__(cls, args, kwargs)

    def full(self, mixin=None):
        if mixin:
            assert mixin.lower() in MIXINS.keys(), 'mixin must be one of {0}'.format(MIXINS.keys())
            mix_obj = MIXINS[mixin]
            mix_obj._full(self)

метакласс

Это работает, когда у меня есть только один миксин (например, TestA, TestB), но не работает, когда у меня есть несколько миксинов (например, TestAB).Я не могу заставить это работать с full методом.Есть ли способ, чтобы метакласс определил новый метод full в моем классе, который контролирует, как он вызывается в каждом экземпляре?Я не могу понять, как правильно это сделать.Я использую __call__ здесь, потому что я хотел контролировать, как создается класс при первом вызове каждого экземпляра.

class MixMeta(abc.ABCMeta):

    def __new__(cls, *args, **kwargs):

        return super(MixMeta, cls).__new__(cls, *args, **kwargs)

    def __call__(cls, *args, **kwargs):
        print('---')
        print('call mixmeta', cls, args, kwargs)

        mixins = [obj for obj in cls.__bases__ if 'Mixin' in obj.__name__]

        newclass = super(MixMeta, cls).__call__(*args, **kwargs)
        if len(mixins) == 1:
            setattr(newclass, 'full', mixins[0](*args, **kwargs)._full)
        else:
            setattr(newclass, 'full', cls.full)
        return newclass

    def full(self, mixin=None):
        print('in full', self)
        if mixin:
            assert mixin.lower() in MIXINS.keys(), 'mixin must be one of {0}'.format(MIXINS.keys())
            mix_obj = MIXINS[mixin]
            print('mixobj', mix_obj)
            mix_obj(*self._args, **self._kwargs)._full()

базовые объекты

# mixmeta class
class BaseObject(six.with_metaclass(MixMeta, object)):

# mixin manager 
class BaseObject(six.with_metaclass(abc.ABCMeta, MixinManager, object)):

    def __new__(cls, *args, **kwargs):

        print('in base object new first', cls.__bases__)

        return super(BaseObject, cls).__new__(cls, args, kwargs)

    def __init__(self, *args, **kwargs):
        super(BaseObject, self).__init__(*args, **kwargs)


class TestBase(BaseObject):
    pass


class TestA(BaseObject, AMixin):
    pass


class TestB(BaseObject, BMixin):
    pass


class TestAB(BaseObject, AMixin, BMixin):
    pass

Создание экземпляров каждого класса

base = TestBase()
a = TestA(db_name='dba') (would like name='dba'; maps to db_name)
b = TestB(ext_name='extb') (would like name='extb' ...)
ab = TestAB(db_name='dba') (name ambiguous; this maps to db_name)
ab1 = TestAB(db_name='dba', ext_name='extb') (same as above, both names map)
ab2 = TestAB(db_name='dba', ext_name='extb')

С MixinManager это работает правильно.С MixMeta он не может правильно звонить.

a.full()  # should print from AMixin
b.full()  # should print from BMixin
ab.full() # should not print anything or throw ambiguity error
ab1.full(mixin='a') # should print from AMixin
ab2.full(mixin='b') # should print from BMixin

Точно так же я хочу сделать то же самое со свойствами.Я хотел бы сопоставить общее свойство name с _name, и оно динамически сопоставляется с тем, что вызывается mixin.Однако имена не должны переопределяться, когда несколько миксинов объединяются вместе.

a.has_name()  # should find and print db_name from AMixin (only db_name set)
b.has_name()  # should find and print ext_name from BMixin (only ext_name set)
ab.has_name() # should print nothing or throw ambiguity error
ab1.has_name(mixin='a') # should print db_name (both db and ext names set)
ab2.has_name(mixin='b') # should print ext_name (both db and ext names set)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...