Как динамически добавлять миксины в качестве базовых классов, не получая ошибок MRO?

Скажи у меня классA, B а такжеC.

Учебный классA а такжеB оба смешанные классы для классаC.

<code>class A( object ):
    pass
class B( object ):
    pass
class C( object, A, B ):
    pass
</code>

Это не будет работать при создании экземпляра класса C. Я должен был бы удалитьobject из класса С, чтобы заставить его работать. (В противном случае у вас возникнут проблемы с MRO).

TypeError: Error when calling the metaclass bases
Cannot create a consistent method resolution
order (MRO) for bases B, object, A

Однако мой случай немного сложнее. В моем случае классC этоserver гдеA а такжеB будут плагины, которые загружаются при запуске. Они находятся в своей собственной папке.

У меня также есть класс с именемCfactory, В Cfactory у меня есть__new__ метод, который создаст полностью функциональный объект C. В__new__ метод яsearch для плагинов, загрузите их, используя__import__, а затем назначить ихC.__bases__ += (loadedClassTypeGoesHere, )

Таким образом, возможно следующее: (сделано довольно абстрактно)

<code>class A( object ):
    def __init__( self ): pass
    def printA( self ):   print "A"
class B( object ):
    def __init__( self ): pass
    def printB( self ):   print "B"
class C( object ):
    def __init__( self ):  pass
class Cfactory( object ):
    def __new__( cls ):
        C.__bases__ += ( A, )
        C.__bases__ += ( B, )
        return C()
</code>

Это снова не будет работать, и снова выдаст ошибки MRO:

TypeError: Cannot create a consistent method resolution
order (MRO) for bases object, A

Легким решением проблемы является удалениеobject базовый класс отA а такжеB, Однако это сделает их объектами старого стиля, которых следует избегать, когда эти плагины запускаются в автономном режиме (что должно быть возможно, для UnitTest)

Еще одно простое решение - удалениеobject отC но это также сделает его классом старого стиля иC.__bases__ будет недоступен, поэтому я не могу добавить дополнительные объекты в базуC

Что было бы хорошим архитектурным решением для этого и как бы вы сделали что-то подобное? Пока я могу жить с классами старого стиля для самих плагинов. Но я их не использую.

 Mark Ransom13 апр. 2012 г., 00:27
Классы старого стиля не существуют в Python 3, поэтому вам в конечном итоге понадобится решение.
 Daan Timmer13 апр. 2012 г., 00:41
Совершенно верно.

Ответы на вопрос(3)

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

Во-первых, почемуCfactory класс, и почему его__new__ метод вернуть экземпляр чего-то еще? Это выглядит как причудливый способ реализовать то, что, естественно, является функцией.Cfactory как вы это описали (и показали упрощенный пример), совсем не ведет себя как класс; у вас нет нескольких его экземпляров, которые разделяют функциональность (фактически, похоже, что вы сделали невозможным создание экземпляров естественным образом).

Если честно,C для меня это тоже не очень похоже на урок. Кажется, что вы не можете создавать более одного его экземпляра, в противном случае вы получите постоянно растущий список баз. Так что делаетC в основном модуль, а не класс, только с дополнительным шаблоном. Я стараюсь избегать "класса с одним экземпляром для представления приложения или какой-либо внешней системы" шаблон (хотя я знаю, что это популярно, потому что Javarequires что вы им пользуетесь). Но механизм наследования классов часто может быть полезен для вещей, которые на самом деле не являются классами, таких как система плагинов.

Я бы сделал это с помощью методаC найти и загрузить плагины, вызываемые модулем, определяющимC так что этоalways в хорошем состоянии. В качестве альтернативы вы можете использовать метакласс, чтобы автоматически добавлять любые плагины, которые он находит, к базам классов. Смешивание механизма для настройкиclass в механизме создания экземпляра класса кажется неправильным; это противоположно гибкому разделенному дизайну.

Если плагиныcan't быть загруженным в то времяC будет создан, тогда я бы вручную вызвал метод класса конфигуратора в тот момент, когда вы можете искать плагины, до того, какC Экземпляр создан.

На самом деле, если класс не может быть приведен в согласованное состояние, как только он создан, я бы, скорее, предпочел бы создание динамического класса, чем модификацию основ существующего класса. Затем система не блокируется в один раз конфигурируемый класс и один раз создается его экземпляр; Вы, по крайней мере, открыты для возможности иметь несколько экземпляров с различными загруженными наборами плагинов. Что-то вроде этого:

def Cfactory(*args, **kwargs):
    plugins = find_plugins()
    bases = (C,) + plugins
    cls = type('C_with_plugins', bases, {})
    return cls(*args, **kwargs)

Таким образом, у вас есть один вызов, чтобы создать свойC экземпляр with дает вам правильно настроенный экземпляр, но у него нет странных побочных эффектов для любых других гипотетических случаевC это может уже существовать, и его поведение не зависит от того, было ли оно выполнено ранее. Я знаю, что вам, вероятно, не нужно ни одно из этих двух свойств, но оно содержит чуть больше кода, чем в вашем упрощенном примере, и зачем ломать концептуальную модель того, что представляют собой классы, если вам это не нужно?

 18 окт. 2014 г., 14:14
Мне нравится этот ответ, за исключением функции Cfactory: она создает один класс, для которого экземпляры могут иметь разные базовые классы в зависимости от найденных плагинов. Имя класса каждого экземпляра больше не имеет значения. Скорее, Cfactory должен создать имя класса, уникальное для комбинации плагинов, включенных в качестве баз, но также идентичное независимо от порядка, в котором были найдены плагины.
 Daan Timmer13 апр. 2012 г., 10:03
Ваш совет относительно того, что Cfactory является классом, а не функцией, также был предоставлен agf. И я изменил это в своем собственном коде. Внутри C будет несколько общих методов, которые всегда должны быть доступны, даже если нет плагинов. Я получу +1 ваш ответ, потому что это хороший ответ, но ответ agf дал решение моей текущей проблемы. Тем не менее, спасибо.
Решение Вопроса

Подумайте об этом так - вы хотите, чтобы миксины перекрывали некоторые из поведенийobjectпоэтому они должны быть раньшеobject в порядке разрешения метода.

Таким образом, вам нужно изменить порядок баз:

class C(A, B, object):
    pass

Из-заэта ошибка, тебе нужноC не наследовать от объекта напрямую, чтобы можно было правильно назначить__bases__и фабрика действительно может быть просто функцией:

class FakeBase(object):
    pass

class C(FakeBase):
    pass

def c_factory():
    for base in (A, B):
        if base not in C.__bases__:
            C.__bases__ = (base,) + C.__bases__
    return C()
 13 апр. 2012 г., 00:40
@ ДаанТиммер. К сожалению, ошибка путает проблему правильного порядка баз.
 Daan Timmer13 апр. 2012 г., 00:38
Да, я попробовал ваш первый пример, который действительно давал эту ошибку (нашел ее также с вашим кодом до редактирования). Позвольте мне проработать несколько вещей и посмотреть, работает ли это.

Существует простой обходной путь: создайте вспомогательный класс с хорошим именем, напримерPluginBase, И используйте это наследование, а не объект.

Это делает код более читабельным (imho), и это приводит к ошибке.

class PluginBase(object): pass
class ServerBase(object): pass

class pluginA(PluginBase): "Now it is clearly a plugin class"
class pluginB(PluginBase): "Another plugin"

class Server1(ServerBase, pluginA, pluginB): "This works"
class Server2(ServerBase): pass
Server2.__base__ += (PluginA,) # This also works

Примечание: возможно, вам не нужноfactory; это необходимо в C ++, но вряд ли в Python

 29 янв. 2015 г., 19:51
Я использую Python 2.7.6 на OS X, и ваша последняя строка не работает. Я получаю "TypeError: неподдерживаемый тип (ы) операнда для + =:" type & apos; и «кортеж»; (после исправления опечатки столицы П).

Ваш ответ на вопрос