Extending classes __init__behavior for newbies

Michael Sparks sparks.m at gmail.com
Mon Feb 14 18:55:01 CET 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