Reputation: 13768
I have an abstract base class (called Shape
below). And I derive a couple classes from it (Circle
and Square
below).
I would like to create a "dispatching class" (called MagicShape
below) so that when I instantiate a new object of this new class, it magically becomes one of the derived classes above based on a passed parameter.
I.e. if Circle
and Square
both get initialized with 2 parameters, I want MagicShape
to take 3 parameters at instantiation so that the first parameter would either be the string circle
or the string square
and would result in creating either a Circle
or Square
with the subsequently specified parameters.
So for specifics, I have:
from numpy import pi as PI
class Shape(object):
def __init__(self, color, size):
self.color=color
self.size = size
def describe(self):
return 'I am a {color:s} {kind:s} of size {size:0.1f}'.format(color=self.color,
kind=self.kind,
size=self.size)
class Circle(Shape):
def __init__(self, color, size):
self.kind = 'circle'
super(Circle, self).__init__(color, size)
def area(self):
return PI * self.size * self.size
class Square(Shape):
def __init__(self, color, size):
self.kind = 'square'
super(Square, self).__init__(color, size)
def area(self):
return self.size * self.size
And I would like to have something like:
class MagicShape(???):
def __init__(self, kind, color, size):
# what goes in here?
So that when I run ms = MagicShape('circle', 'red', 3)
, ms
is a Circle
but when I run ms = MagicShape('square', 'blue', 2)
, ms
is a Square
.
I know that I could do something like this:
def make_shape(kind, color, size):
if 'circle'==kind:
return Circle(color, size)
elif 'square'==kind:
return Square(color, size)
else:
raise ValueError
and do the "dispatching" via function. But somehow this felt like it should be doable with classes. Can someone set me straight?
Upvotes: 1
Views: 86
Reputation: 809
You want to use a factory method. Pretty much just as you did, but you can pull it under your super Shape class.
from numpy import pi as PI
class Shape(object):
#we'll use this below factory method to return the correct subclass:
@classmethod
def getShape(self,kind,color,size):
if 'circle'==kind:
return Circle(color, size)
elif 'square'==kind:
return Square(color, size)
else:
raise ValueError
def __init__(self, color, size):
self.color=color
self.size = size
def describe(self):
return 'I am a {color:s} {kind:s} of size {size:0.1f}'.format(color=self.color,
kind=self.kind,
size=self.size)
class Circle(Shape):
def __init__(self, color, size):
self.kind = 'circle'
super(Circle, self).__init__(color, size)
def area(self):
return PI * self.size * self.size
class Square(Shape):
def __init__(self, color, size):
self.kind = 'square'
super(Square, self).__init__(color, size)
def area(self):
return self.size * self.size
Upvotes: 0
Reputation: 1121834
You could just use a function, no class required:
shapes = {shape.__name__.lower(): shape
for shape in Shape.__subclasses__()}
def MagicShape(kind, color, size):
try:
return shapes[kind](color, size)
except KeyError:
raise ValueError(kind)
The class.__subclasses__()
method here returns all subclasses of Shape
, making for a quick and handy way to build a map from kind
string to class.
Remember that creating a class is just another call. There is no difference between:
class Foo(object):
def __init__(self, arg1, arg2):
pass
and
def Foo(arg1, arg2):
return something_that_is_an_instance
from the caller's point of view; they'd just use:
result = Foo(value1, value2)
for both the class and the function.
Upvotes: 4
Reputation: 3208
The thing you'd probably want to do (factory/dispatch function) is already outlined in other answers/comments here, but if you actually wanted to return a different class instance and make it look like you're only making a MagicClass
, you could do something fancy/awful with the __new__
method, like so:
class MagicClass(object):
def __new__(cls, *args, **kwargs):
# if args[whatever] == 'square': return Square(*args, **kwargs)
...or something like that. It's not good practice, but it is doable.
Upvotes: 0
Reputation: 2860
You want to use the factory pattern, this example does almost exactly what you are asking: http://python-3-patterns-idioms-test.readthedocs.org/en/latest/Factory.html
Upvotes: 0