4

So, i'm trying to learn python and every time i post a question here it feels like giving in...

I'm trying to make my own class of turtle.Turtle.

    import turtle
class TurtleGTX(turtle.Turtle):
    """My own version of turtle"""
    def __init__(self):
        pass

my_turtle = TurtleGTX()
my_turtle.forward(10)

Gives the Traceback: AttributeError: 'TurtleGTX' object has no attribute '_position'. Which I then learn is a "private vairable" which according to the offical python tutorial i can mangle/override in my subclass TurtleGTX. How to do this with a program as large as turtle seems rather difficult and implies i'm missing a simpler solution to the problem. In the end i learned and that was the point but i would still like to run it by the community to see if there is a elegant way to create a subclass of turtle.Turtle. (The next step is to have your turtle behave different then the standard turtle)

So a comment below made me think that maybe i could do this:

import turtle
class TurtleGTX(turtle.Turtle):
    """My own version of turtle"""


my_turtle = TurtleGTX()
my_turtle.forward(100)

which actual runs! Now i'm going to see where that leads me... something tells me that i might have gone 1 step forward two step back as this means i won't be able to initialize anything on my subclass...

ggorlen
  • 44,755
  • 7
  • 76
  • 106
Drew Verlee
  • 1,880
  • 5
  • 21
  • 30

3 Answers3

9

Rounding up Ignacio's answer and orokusaki's comment you probably should write something like

import turtle
class TurtleGTX(turtle.Turtle):
    """My own version of turtle"""
    def __init__(self,*args,**kwargs):
        super(TurtleGTX,self).__init__(*args,**kwargs)
        print("Time for my GTX turtle!")

my_turtle = TurtleGTX()
my_turtle.forward(100)
Odomontois
  • 15,918
  • 2
  • 36
  • 71
  • Thanks for the help, I might have mis-understood the question the tutorial was asking as they never introduced super, *args or **kwargs... – Drew Verlee Feb 25 '12 at 14:39
3

If you redefine a method (such as __init__()) in a child class then it is your responsibility to call the parent's method in order to have the parent's behavior respected.

Ignacio Vazquez-Abrams
  • 776,304
  • 153
  • 1,341
  • 1,358
  • Also note, your __init__ needs to accept the same args, kwargs as the parent and you need to call the super() init with the args, kwargs, unless you intend to rewrite the interface. – orokusaki Feb 25 '12 at 05:26
2

It's a tempting pattern to subclass Turtle and add custom functionality, say, turtle.jump(). But I've seen a good deal of problems caused by this pattern and caution against it.

Calling the Turtle() initializer (including subclasses of Turtle) registers the object with the turtle module's internal list of turtles. This list has _update() called on it per frame if you don't disable the internal update loop with turtle.tracer(0). This internal turtle isn't likely to get garbage collected as your subclass might expect.

In addition to the update/draw internals they're hooked into, Turtle has many properties and methods that can easily name clash with your custom properties and methods (there are about 150 such names in the turtle module, not counting dunderscores). When calling, say, self.left(), it can be hard to remember if this is a turtle method call on the superclass or your derived class. By composing the turtle as a property rather than a subclass, self.turtle.left() makes the ownership completely clear, at the cost of a bit of verbosity.

I've also seen cases where inheritance and composition are mixed together, leading to a confused state.

Here's a collection of alternatives to inheritance, some of which are only useful for specific purposes but listed here anyway for completeness:

  • If you don't mind the turtles not being garbage collected (you are making a fixed amount that you plan to keep around), you could allocate instances of a custom class, each of which has an internal self.turtle = Turtle() instance that is manipulated in the normal way from the wrapper. This is the composition approach.

    • Since you're probably only using a handful of turtle methods, you can compromise a bit and add a self.left(n) method on your composed class and delegate to the underlying self.turtle.left(n). This involves adding a few extra methods but avoids all of the pitfalls of inheritance.
  • Instead of classes, you could make Turtle instances and pass them to functions that cause them to take an action in a C-style imperative manner: def jump(turtle, height): ....

  • If you plan to create particles of some sort that are created and destroyed relatively frequently and you just need a turtle to render rather than represent them, you might store the particles' internal representations and physics properties in a class alongside a reference to a single "pen" turtle. When drawing is needed, tracing is disabled and the turtle is moved from one position to the next among all shared particles, drawing as needed.

    In theory, this is a great solution, but in practice, I've found it to be too much of a performance impact to bear, even with tracer off.

  • Custom classes can borrow from an object pool of turtles, helping to mitigate the garbage collection problem. See Why is my Python Turtle program slowing down drastically the longer it runs? for details.

That said, I'd be eager to see a useful, non-trivial case of the subclassing pattern if anyone has one as a counterargument.

ggorlen
  • 44,755
  • 7
  • 76
  • 106