I'm wondering how to create a metaclass in Python that can create other classes that:
- Store their instances in an array automatically
- Have a special instance,
NonMetaClass.all
, whose properties:- When set, set all the class's instances with the same key to the same value (e.g.,
Foo.all.num = 3
makes all instances ofFoo
have anum
of 3) - When accessed (get), returns an array of all of the class's instances's key values (e.g.,
Foo.all.num
returns[5, 3, 2]
) - Cannot be deleted.
- When called (if the attribute is a function), call that method on all the instances of a class.
- When set, set all the class's instances with the same key to the same value (e.g.,
In Python terms, I would like to turn a class that is like this:
class Foo(object):
BAR = 23
def __init__(self):
self.a = 5
def pointless():
print 'pointless.'
def change_a(self):
self.a = 52
Into this:
class Foo(object):
BAR = 23
instances = []
all = # Some black magic to create the special "all" instance
def __init__(self):
self.a = 5
Foo.instances.append(self)
def pointless(self):
print 'pointless.'
def change_a(self):
self.a = 52
And be able to use it like this:
>>> Foo()
>>> Foo.instances[0]
<__main__.Foo instance at 0x102ff5758>
>>> Foo()
>>> len(Foo.instances)
2
>>> Foo.all.a = 78
78
>>> Foo.all.a
[78, 78]
>>> Foo.all.change_a()
>>> Foo.all.a
[52, 52]
>>>
Ok, I figured out how to do this for Python 2.7 on my own. This is what I believe to be the best solution though it may not be the only one. It allows you to set, get, and function call on attributes of
Class.all
. I've named the metaclassInstanceUnifier
, but please comment if you think there's a better (shorter, more descriptive) name you can think of.The only thing a metaclass is needed for there is actually quite easy: exactly creating the
intances
andall
attributes.All it have to do is to insert those into the namespace. Ah, it will also have to wrap the class
__new__
method to insert new instances into theinstances
list.The part that is the behavior wanted from
all
is interesting, and that can be implemented using the descriptor protocol, and attribute access control, so we have to craft a couple special classes, that will return the appropriate objects when requested after the "."."All" is the class that will be instantiated as "all" - it just needs a
__get__
method to return another special object, from theAllAttr
class, already bound to the parent class."AllAttr" is a special object that on any attribute access, perform your requirements on the members of the owner class "instance" attribute.
And "CallAllList" is a special list subclass that is callable, and calls all its members in turn. It is used by AllAttr if the required attribute from the owner class is callable itself.
The code above is written so that it is Python 3 and Python 2 compatible, since you appear to still be using Python2 given your "print" example. The only thing that cannot be written compatible with both forms is the metaclass using declaration itself - just declare a
__metaclass__ = MetaAll
inside the body of your Foo class if you are using Python 2. But you should not really be using Python2, just change to Python 3 as soon as you can.update
It happens that Python 2 has the "unbound method" figure, and the special casing of
__new__
does not work like in Python 3: you can't just attribute a function named__new__
to the class. In order to get the correct__new__
method from the superclasses, the easiest way is to create a disposable class, so that it can be searched linearly. Otherwise, one would have to reimplement the MRO algorithm to get the proper__new__
method.So, for Python 2, the metaclass should be this:
(now, again, this thing is ancient. Just settle for Python 3.6)