Как мне создать фабрику Mixin в Python? - PullRequest
3 голосов
/ 01 февраля 2012

У меня есть несколько классов, которые обернуты другими классами для добавления новых функций.

К сожалению, классы-обертки не реализуют функции прохода через классы, которые обертывают, поэтому обертка не может бытьиспользуется взаимозаменяемо с исходным классом.

Я хотел бы динамически создавать классы, которые содержат функциональные возможности как обертки, так и исходного класса.

У меня была идея создать смешанныйКласс и использовать фабрику, чтобы применить его к существующему классу, чтобы динамически создать новый класс двойного использования.Это должно позволить мне написать смешанный модуль один раз, и смешанный класс будет использоваться для обеспечения либо исходной функциональности, либо расширенной функциональности из смешанного через один объект.

Это сортировкаиз вещей, которые я ищу:

class A:
    def __init__(self):
        self.name = 'A'

    def doA(self):
        print "A:", self.name


class B(A):
    def __init__(self):
        self.name = 'B'

    def doB(self):
        print "B:", self.name


class C(A):
    def __init__(self):
        self.name = 'C'

    def doC(self):
        print "C:", self.name


class D:
    def doD(self):
        print "D:", self.name


class BD(B,D):
    pass


def MixinFactory(name, base_class, mixin):
    print "Creating %s" % name
    return class(base_class, mixin)     # SyntaxError: invalid syntax

a, b, c, d, bd = A(), B(), C(), D(), BD()

bd2 = MixinFactory('BD2', B, D)()
cd = MixinFactory('CD', C, D)()

a.doA()     # A: A

b.doA()     # A: B
b.doB()     # B: B

c.doA()     # A: C
c.doC()     # C: C

bd.doA()    # A: B
bd.doB()    # B: B
bd.doD()    # D: B

bd2.doA()   # A: B
bd2.doB()   # B: B
bd2.doD()   # D: B

cd.doA()    # A: C
cd.doC()    # C: C
cd.doD()    # D: C

Проблема в том, что очевидно, что вы не можете просто вернуть класс из функции.Несмотря на синтаксическую ошибку, приведенный выше код действительно показывает, чего я пытаюсь достичь.

Я поиграл с вариантом трех аргументов type(), но не смог заставить его работать, поэтому я 'Я не уверен, что это правильный подход.

Я предполагаю, что создание фабрики смешивания такого типа возможно в в Python, так что мне нужно понять, чтобы реализовать его?


Как прокомментировал Niklas R , этот ответ на вопрос Динамическое наследование Python: как выбрать базовый класс при создании экземпляра? предоставляет решение для моего запроса, но ответ Ben здесь дает лучшее объяснение почему.

Ответы [ 2 ]

6 голосов
/ 01 февраля 2012

На самом деле вы можете вернуть класс из функции. Ваша синтаксическая ошибка в том, что вы используете ключевое слово класса , как если бы это была функция, которую вы можете вызвать.

Помните, что все блоки классов для - это создание нового класса, а затем привязка к нему выбранного вами имени (в текущей области видимости). Так что просто поместите блок класса в вашу функцию, а затем верните класс!

def mixinFactory(name, base, mixin):
    class _tmp(base, mixin):
        pass
    _tmp.__name__ = name
    return _tmp

Как отметил ejucovy , вы также можете позвонить type напрямую:

def mixinFactory(name, base, mixin):
    return type(name, (base, mixin), {})

Это работает, потому что это (обычно) то, что фактически делает блок класса; он собирает все имена, которые вы определяете в блоке класса, в словарь, затем передает имя класса, кортеж базовых классов и словарь в type для создания нового класса.

Однако type - это не что иное, как метакласс по умолчанию . Классы являются объектами, как и все остальное, и являются экземплярами класса. Большинство классов являются экземплярами type, но если задействован другой метакласс, вам следует вызывать его вместо type, точно так же, как вы не назвали бы object, чтобы создать новый экземпляр вашего класса.

Ваш миксин (предположительно) не определяет метакласс, поэтому он должен быть совместим с любым метаклассом, который является подклассом type. Так что вы можете использовать любой класс base:

def mixinFactory(name, base, mixin):
    return base.__class__(name, (base, mixin), {})

Однако комментарий С. Лотта действительно кажется лучшим «ответом» на этот вопрос, если только ваша фабрика микшеров не делает ничего более сложного, чем просто создание нового класса. Это намного понятнее и менее типично, чем любой из предложенных вариантов создания динамического класса:

class NewClass(base, mixin):
    pass
4 голосов
/ 01 февраля 2012

Вы можете иметь объявления классов где угодно, а объявления классов могут ссылаться на переменные для создания подклассов.Что касается имени класса, то это просто атрибут .__name__ объекта класса.Итак:

def MixinFactory(name, base_class, mixin):
  print "Creating %s" % name
  class kls(base_class, mixin):
    pass
  kls.__name__ = name
  return kls

должно это сделать.

Для однострочной функции функция с тремя аргументами type также должна работать:

def MixinFactory(name, base_class, mixin):
  return type(name, (base_class, mixin), {})
...