Extending classes __init__behavior for newbies
Michael Sparks
sparks.m at gmail.com
Mon Feb 14 12:55:01 EST 2011
It can be broken if someone tries to use the class as is - that is
treating the
class as a model - to drive a display of the ship. If it was written
using super()
then that wouldn't be a problem.
For example, I could write a display mixin that I'd like to use like
this:
class VisibleShip(ship, sprite):
...
and
class FasterVisibleShip(faster_ship, sprite):
...
But because neither ship nor faster_ship call their super class
correctly,
this won't work.
To turn this into something concrete, consider the following code:
class mixin(object):
"""
The key benefit of this mixin is that it tracks instances created
and
also allows subclasses to have inheritable default values.
"""
objects = []
def __init__(self, **kwargs):
super(mixin, self).__init__()
self.__dict__.update(**kwargs)
self.objects.append(self)
Using that code, I can create my ship model as a mixin:
class ship(mixin):
x = 0
y = 0
step = 1
angle = 0
name = ""
def update(self):
self.x = self.x + math.cos(math.radians(self.angle)) *
self.step
self.y = self.y + math.sin(math.radians(self.angle)) *
self.step
As well as the faster ship:
class fastership(ship):
speed = 1
def update(self):
self.x = self.x + math.cos(math.radians(self.angle)) *
self.step * self.speed
self.y = self.y + math.sin(math.radians(self.angle)) *
self.step * self.speed
I can then create a sprite mixin:
class sprite(mixin):
x = 0
y = 0
image = "sprite.png"
display = None
def __init__(self, **kwargs):
super(sprite, self).__init__(**kwargs)
self._image = pygame.image.load(self.image)
print "Image Loaded", self.__class__
def render(self,surface):
# assume surface is something like a pygame surface
surface.blit(self._image, (self.x, self.y))
And also for debug purposes a debug mixin:
import time
class debug(mixin):
debug_file = "debug.log"
debug_handle = None
def __init__(self, **kwargs):
print "Creation arguments", self.__class__, kwargs
super(debug, self).__init__(**kwargs)
@classmethod
def dump_state(klass,self):
if klass.debug_handle is None:
klass.debug_handle = open(self.debug_file, "w")
klass.debug_handle.write(str(time.time()) + ":" +
str(klass) + ":" + str(self.__dict__)+"\n\n")
klass.debug_handle.flush()
Using these we can create visible ships which can also send debug
information to a file when they're pinged:
class VisibleShip(ship, sprite, debug):
x = 300
y = 300
image = "ship.png"
class FasterVisibleShip(fastership, sprite, debug):
x = 400
y = 400
image = "fastship.png"
And use them like this:
import pygame
pygame.init()
display = pygame.display.set_mode((800, 600), 0)
ship_one = VisibleShip(step=1)
ship_two = FasterVisibleShip(angle = 60)
t = time.time()
while time.time()-t <1 :
display.fill((0,0,0))
for sprite in mixin.objects:
sprite.render(display)
sprite.dump_state(sprite)
sprite.update()
pygame.display.flip()
time.sleep(0.05)
Now, you could do this the other way I mentioned by by using kwargs,
and super in this way
you can get the various classes to play nicely with each other, which
means you can have
clearer code.
After all, our ship / fastership have now become focussed on the code
you want them to model - not
on faff with superclasses:
class ship(mixin):
x = 0
y = 0
step = 1
angle = 0
name = ""
def update(self):
self.x = self.x + math.cos(math.radians(self.angle)) *
self.step
self.y = self.y + math.sin(math.radians(self.angle)) *
self.step
class fastership(ship):
speed = 1
def update(self):
self.x = self.x + math.cos(math.radians(self.angle)) *
self.step * self.speed
self.y = self.y + math.sin(math.radians(self.angle)) *
self.step * self.speed
But not only that they play nicely with debug code and also display
code, and you get
defaults you can inherit at the same time too. If you wrote ship /
fastership with an
explicit init and didn't do the super() call - eg like this:
class ship(object):
def __init__(self,x=0,y=0,step=1,angle=0, name=''):
self.x = x
self.y = y
self.step = step
self.angle = angle
self.name = name
def update(self):
self.x = self.x + math.cos(math.radians(self.angle)) *
self.step
self.y = self.y + math.sin(math.radians(self.angle)) *
self.step
class fastership(ship):
def __init__(self,speed=1):
ship.__init__(self,x=0,y=0,step=1,angle=0, name='')
self.speed = speed
def update(self):
self.x = self.x + math.cos(math.radians(self.angle)) *
self.step * self.speed
self.y = self.y + math.sin(math.radians(self.angle)) *
self.step * self.speed
and mixed in like this:
class VisibleShip(ship, sprite):
image = "ship.png"
class FasterVisibleShip(fastership, sprite):
image = "fastship.png"
Then the code breaks - simply because you're doing this:
ship.__init__(self,x=0,y=0,step=1,angle=0, name='')
rather than:
super(ship,self).__init__()
and
super(fastership,self).__init__()
At the end of the day, both approaches *work* but to differing levels
and
differing levels of flexibility & maintainability.
>From the perspective of fragility though this approach is far more
fragile:
ship.__init__(self,x=0,y=0,step=1,angle=0, name='')
</tuppenceworth>
For convenience I'm copying below my .sig the code I mention above,
for you
to make your own mind up which you find more maintainable. (Consider
extending
sprite handling, or the complexity of the ship model)
Michael.
--
http://www.kamaelia.org/Home , http://twitter.com/kamaelian
#!/usr/bin/python
class mixin(object):
"""
The key benefit of this mixin is that it tracks instances created
and
also allows subclasses to have inheritable default values.
"""
objects = []
def __init__(self, **kwargs):
super(mixin, self).__init__()
self.__dict__.update(**kwargs)
self.objects.append(self)
import math
class ship(mixin):
x = 0
y = 0
step = 1
angle = 0
name = ""
def update(self):
self.x = self.x + math.cos(math.radians(self.angle)) *
self.step
self.y = self.y + math.sin(math.radians(self.angle)) *
self.step
class fastership(ship):
speed = 1
def update(self):
self.x = self.x + math.cos(math.radians(self.angle)) *
self.step * self.speed
self.y = self.y + math.sin(math.radians(self.angle)) *
self.step * self.speed
class sprite(mixin):
x = 0
y = 0
image = "sprite.png"
display = None
def __init__(self, **kwargs):
super(sprite, self).__init__(**kwargs)
self._image = pygame.image.load(self.image)
print "Image Loaded", self.__class__
def render(self,surface):
# assume surface is something like a pygame surface
surface.blit(self._image, (self.x, self.y))
import time
class debug(mixin):
"Not perfect at all IMO"
debug_file = "debug.log"
debug_handle = None
def __init__(self, **kwargs):
print "Creation arguments", self.__class__, kwargs
super(debug, self).__init__(**kwargs)
@classmethod
def dump_state(klass,self):
if klass.debug_handle is None:
klass.debug_handle = open(self.debug_file, "w")
klass.debug_handle.write(str(time.time()) + ":" + str(klass) +
":" + str(self.__dict__)+"\n\n")
klass.debug_handle.flush()
class VisibleShip(ship, sprite, debug):
x = 300
y = 300
image = "ship.png"
class FasterVisibleShip(fastership, sprite, debug):
x = 400
y = 400
image = "fastship.png"
import pygame
pygame.init()
display = pygame.display.set_mode((800, 600), 0)
ship_one = VisibleShip(step=1)
ship_two = FasterVisibleShip(angle = 60)
t = time.time()
while time.time()-t <1 :
display.fill((0,0,0))
for sprite in mixin.objects:
sprite.render(display)
sprite.dump_state(sprite)
sprite.update()
pygame.display.flip()
time.sleep(0.05)
More information about the Python-list
mailing list