Динамическое подклассирование в Python - PullRequest
4 голосов
/ 20 января 2012

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

Однако иногда это атомарность невозможно обеспечить, поскольку один компонент может зависеть от другого.Например, представьте, что у меня есть компонент, который дает графическое представление объекту, и другой компонент, который использует это графическое представление для выполнения некоторой проверки столкновения.Первый является чисто атомарным, однако последний требует, чтобы текущий объект уже разделил этот компонент графического представления на подклассы, чтобы его методы были доступны для него.Это проблема, потому что мы должны как-то сказать пользователям этой библиотеки, что для того, чтобы использовать определенный Компонент, они также должны создать подкласс этого другого.Мы могли бы сделать этот компонент столкновения подклассом визуального компонента, но если пользователь также подклассирует этот визуальный компонент, он не будет работать, потому что класс не находится на том же уровне (в отличие от простого ромбического отношения, которое желательно), и будетдать загадочные ошибки мета-класса, которые трудно понять программисту.

Поэтому я хотел бы знать, есть ли какой-нибудь крутой способ, возможно, с помощью переопределения метакласса или использования декораторов классов, пометить эти неатомарные компонентыи когда они подклассы, дополнительная зависимость будет введена в текущий объект, если он еще не доступен.Пример:

class AtomicComponent(object):
    pass

@depends(AtomicComponent)  # <- something like this?
class UnAtomicComponent(object):
    pass

class UserClass(UnAtomicComponent): #automatically includes AtomicComponent
    pass

class UserClass2(AtomicComponent, UnAtomicComponent): #also works without problem
    pass

Может кто-нибудь подсказать мне, как я могу это сделать?или если это вообще возможно ...

edit: Поскольку спорным является то, что решение метаклассов является лучшим, я оставлю это неприемлемым в течение 2 дней.

Другие решения могут заключаться в улучшении сообщений об ошибках, например, выполнение чего-то вроде UserClass2 может привести к ошибке, говорящей о том, что UnAtomicComponent уже расширяет этот компонент.Это, однако, создает проблему, состоящую в том, что невозможно использовать два компонента UnAtomicComponent, учитывая, что они будут создавать подклассы объекта на разных уровнях.

1 Ответ

3 голосов
/ 20 января 2012

"Метаклассы"

Вот для чего они!Во время создания класса параметры класса проходят через код метакласса, где вы можете проверить базы и затем изменить их, например.

Это выполняется без ошибок - хотя и не сохраняет порядок помеченных необходимых классовс помощью декоратора «зависимость»:

class AutoSubclass(type):
    def __new__(metacls, name, bases, dct):
        new_bases = set()
        for base in bases:
            if hasattr(base, "_depends"):
                for dependence in base._depends:
                    if not dependence in bases:
                        new_bases.add(dependence)
        bases = bases + tuple(new_bases)
        return type.__new__(metacls, name, bases, dct)



__metaclass__ = AutoSubclass

def depends(*args):
    def decorator(cls):
        cls._depends = args
        return cls
    return decorator


class AtomicComponent:
    pass

@depends(AtomicComponent)  # <- something like this?
class UnAtomicComponent:
    pass

class UserClass(UnAtomicComponent): #automatically includes AtomicComponent
    pass

class UserClass2(AtomicComponent, UnAtomicComponent): #also works without problem
    pass

(я удалил наследование от «объекта», так как объявил глобальную переменную __metaclass__. Все классы по-прежнему будут новым стилем и будут иметь этот метакласс.объект или другой класс переопределяет глобальную переменную __metaclass__, и уровень класса __metclass__ должен быть объявлен)

- редактировать -

БезМетаклассы, путь состоит в том, чтобы ваши классы должным образом наследовали от их зависимостей.Tehy больше не будет таким «атомарным», но, поскольку они не могут работать, будучи такими атомными, это может быть неважно.

В приведенном ниже примере классы C и D будут вашими пользовательскими классами:

>>> class A(object): pass
... 
>>> class B(A, object): pass
... 
>>> 
>>> class C(B): pass
... 
>>> class D(B,A): pass
... 
>>> 
...