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.