Should I use metaclasses here?
I'm reading some data, which I want to create classes out of - this is done at load-time. The classes are grouped into a Special
class, which can only be instantiated using run-time information. The classes depend on this Special
class, so they can only be useful once it is created. Here is some simplified code that shows how I want it to work, using random
instead of actual run-time info:
import random
def make_foo(param1, param2):
class Foo:
def __init__(self, special):
self.param1 = param1
self.param2 = param开发者_C百科2
self.special = special
def do(self):
print "%s is doing" % self
def __str__(self):
return "Foo<%s,%s with %s>" % (self.param1, self.param2,
self.special)
return Foo
def make_bar(foo):
class Bar:
def __init__(self, special):
self.foo = foo(special)
def do(self):
print "%s is doing" % (self,)
def __str__(self):
return "Bar<%s>" % self.foo
return Bar
def make_grouper(foobars):
class Grouper:
def __init__(self, special):
self.foobars = [foobar(special) for foobar in foobars]
return Grouper
def make_special(howtomake, groups):
class Special:
def __init__(self):
self.important = random.choice(howtomake)
self.groups = [group(self) for group in groups]
def __str__(self):
return "Special<%s>" % self.important
return Special
Foo10_20 = make_foo(10, 20)
Foo30_40 = make_foo(30, 40)
Bar = make_bar(Foo10_20)
Grouper1 = make_grouper([Foo10_20, Foo30_40])
Grouper2 = make_grouper([Bar, Bar])
Special = make_special("IMPORTANTINFO", [Grouper1, Grouper2])
s = Special()
s.groups[0].foobars[0].do()
s.groups[0].foobars[1].do()
s.groups[1].foobars[0].do()
s = Special()
s.groups[0].foobars[0].do()
s.groups[0].foobars[1].do()
s.groups[1].foobars[0].do()
Sample output:
Foo<10,20 with Special<O>> is doing
Foo<30,40 with Special<O>> is doing
Bar<Foo<10,20 with Special<O>>> is doing
Foo<10,20 with Special<I>> is doing
Foo<30,40 with Special<I>> is doing
Bar<Foo<10,20 with Special<I>>> is doing
It can be summarized as having to create a set of classes which need to be bound to a special
argument (so all the constructors, once the classes are done, just take a special
argument). Can this be done more elegantly using meta-classes, or is this code fine the way it is?
Since I generally prefer classes of closures in Python, I'd use factory classes here and avoid the use of dynamically created classes alltogether. Example:
class Foo:
def __init__(self, param1, param2, special):
self.param1 = param1
self.param2 = param2
self.special = special
def do(self):
print "%s is doing" % self
def __str__(self):
return "Foo<%s,%s with %s>" % (self.param1, self.param2,
self.special)
class FooFactory:
def __init__(self, param1, param2):
self.param1 = param1
self.param2 = param2
def __call__(self, special):
return Foo(self.param1, self.param2, special)
foo_factory = FooFactory(1, 2)
foo = foo_factory(3)
An alternative to FooFactory
would be the use of functools.partial()
. If Foo
is defined as above, you could do
FooSpecialised = functools.partial(Foo, param1, param2)
and create instances of Foo
by using
FooSpecialised(special)
This is not strictly an answer (or maybe it is) but I think this quote (MarkLutz, learning python 4 ed) of a quote from Tim Peters could be interesting:
To borrow a quote from the comp.lang.python newsgroup by veteran Python core developer Tim Peters (who is also the author of the famous “import this” Python motto):
[Metaclasses] are deeper magic than 99% of users should ever worry about. If you wonder whether you need them, you don’t (the people who actually need them know with certainty that they need them, and don’t need an explanation about why).
I ended up making a base class:
class Base(object):
@classmethod
def bind_init(cls, *args, **kwargs):
def init(special):
return cls(special, *args, **kwargs)
return init
Foo et. al. are a normal-looking non-dynamic classes now:
class Foo(Base):
def __init__(self, special, param1, param2):
self.param1 = param1
self.param2 = param2
self.special = special
def do(self):
print "%s is doing" % self
def __str__(self):
return "Foo<%s,%s with %s>" % (self.param1, self.param2,
self.special)
and they are used as follows:
Foo10_20 = Foo.bind_init(10, 20)
Foo30_40 = Foo.bind_init(30, 40)
Bar = Bar.bind_init(Foo10_20)
Grouper1 = Grouper.bind_init([Foo10_20, Foo30_40])
Grouper2 = Grouper.bind_init([Bar, Bar])
Special = Special.bind_init("IMPORTANTINFO", [Grouper1, Grouper2])
this minimizes code repetition (don't have to make tons of factory classes) and I also like the lingo much more than class factory.
精彩评论