I have a number of atomic classes (Components/Mixins, not really sure what to call them) in a library I'm developing, which are meant to be subclassed by applications. This atomicity was created so that applications can only use the features that they need, and combine the components through multiple inheritance.
However, sometimes this atomicity cannot be ensured because some component may depend on another one. For example, imagine I have a component that gives a graphical representation to an object, and another component which uses this graphical representation to perform some collision checking. The first is purely atomic, however the latter requires that the current object already subclassed this graphical representation component, so that its methods are available to it. This is a problem, because we have to somehow tell the users of this library, that in order to use a certain Component, they also have to subclass this other one. We could make this collision component sub class the visual component, but if the user also subclasses this visual component, it wouldn't work because the class is not on the same level (unlike a simple diamond relationship, which is desired), and would give the cryptic meta class errors which are hard to understand for the programmer.
Therefore, I would like to know if there is any cool way, through maybe metaclass redefinition or using class decorators, to mark these unatomic components, and when they are subclassed, the additional dependency would be injected into the current object, if its not yet available. Example:
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
Can someone give me an hint on how I can do this? or if it is even possible...
edit: Since it is debatable that the meta class solution is the best one, I'll leave this unaccepted for 2 days.
Other solutions might be to improve error messages, for example, doing something like UserClass2 would give an error saying that UnAtomicComponent already extends this component. This however creates the problem that it is impossible to use two UnAtomicComponents, given that they would subclass object on different levels.
-
2I wouldn't go for a metaclass solution for this one. I'd write good documentation, clearly stating the dependencies. If someone fails to fulfil the dependencies, some methods will be missing, resulting in a rather clear error message. I'd consider this a sufficient solution. The advantage of not using a metaclass is simplicity, less code and increased flexibility. The user might want to fulfil a dependency by his own class or in some other way you never thought of. The metaclass solution would take away the option to do this, for very little gain.Sven Marnach– Sven Marnach2012年01月20日 16:51:14 +00:00Commented Jan 20, 2012 at 16:51
-
I was thinking that maybe the init method on the UnAtomicComponent could verify its dependencies, and generate an exception if the current object does not include the dependency componentRui Campos– Rui Campos2012年01月20日 17:20:17 +00:00Commented Jan 20, 2012 at 17:20
-
My suggestion was to not do anything at all. Just document clearly which components are needed. The code will fail anyway if someone fails to include something that is needed, and the error message would state which method is missing. This information should usually be enough to fix the problem, especially if the documentation is clear.Sven Marnach– Sven Marnach2012年01月20日 17:47:34 +00:00Commented Jan 20, 2012 at 17:47
-
Doing nothing is nonsense - even if you decide it is bettrer to proper document, and let the user inherit from all the proper classes, it shuld then raise an error early if not all the classes where used.jsbueno– jsbueno2012年01月20日 18:20:29 +00:00Commented Jan 20, 2012 at 18:20
-
For components a non-inheritance design (e.g. composition) might be better suited and have less issues. In a java world you would use some kind of factory, but a python metaclass is also just a kind of special factory (which can be implemented as a factory too, e.g. something that creates composed classes on the fly and returns instances of those.)schlenk– schlenk2012年01月21日 13:41:25 +00:00Commented Jan 21, 2012 at 13:41
1 Answer 1
"Metaclasses"
This is what they are for! At time of class creation, the class parameters run through the metaclass code, where you can check the bases and change then, for example.
This runs without error - though it does not preserve the order of needed classes marked with the "depends" decorator:
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
(I removed inheritance from "object", as I declared a global __metaclass__ variable. All classs will still be new style class and have this metaclass. Inheriting from object or another class does override the global __metaclass__variable, and a class level __metclass__ will have to be declared)
-- edit --
Without metaclasses, the way to go is to have your classes to properly inherit from their dependencies. Tehy will no longer be that "atomic", but, since they could not work being that atomic, it may be no matter.
In the example bellow, classes C and D would be your User classes:
>>> class A(object): pass
...
>>> class B(A, object): pass
...
>>>
>>> class C(B): pass
...
>>> class D(B,A): pass
...
>>>