初心者向けPython Turtle: turtle.turtles() で始める多タートルプログラミング
もう少し詳しく説明すると、以下のようになります。
-
タートルグラフィックス
turtle
モジュールは、画面上に「タートル」と呼ばれる小さなカーソルを動かすことで図形を描画する仕組みを提供します。これは、プログラミング学習の初期段階で視覚的に結果を確認できるためによく用いられます。 -
複数のタートル
通常、import turtle
とした後にt = turtle.Turtle()
のようにすると、一つタートルが作成されます。しかし、t1 = turtle.Turtle()
、t2 = turtle.Turtle()
のようにすれば、複数のタートルを同時に作成し、それぞれを独立して動かすことができます。
turtle.turtles()
に関連する一般的な問題とトラブルシューティング
タートルが期待通りに表示されない、動かない
これは turtle.turtles()
の直接的なエラーではありませんが、複数のタートルを操作する際に最もよく遭遇する問題です。
考えられる原因と解決策
-
タートルが隠されている (hidden)
- 原因
hideturtle()
メソッドを呼び出していると、タートル自体は存在していても表示されません。 - 解決策
必要であればshowturtle()
を呼び出してタートルを表示させます。
import turtle t = turtle.Turtle() t.hideturtle() t.forward(50) # 隠れたタートルを表示させる t.showturtle() turtle.done()
- 原因
-
アニメーション速度が速すぎる
- 原因
タートルの移動速度(speed()
)が速すぎると、描画が瞬時に行われ、何が起こったか視認できないことがあります。特に複数のタートルが同時に動く場合、動きが混ざって見えにくいことがあります。 - 解決策
turtle.speed()
メソッドを使って速度を調整します。0(最速)から10(最遅)までの値、または文字列("fastest", "fast", "normal", "slow", "slowest")を指定できます。デバッグ時にはspeed(1)
など遅い設定にすると、タートルの動きを追うことができます。
import turtle t1 = turtle.Turtle() t1.speed(1) # 速度を遅くする t1.forward(100) t2 = turtle.Turtle() t2.speed("slow") # または文字列で指定 t2.backward(100) turtle.done()
- 原因
-
- 原因
turtle
プログラムは、描画が完了した後もウィンドウを保持するためにturtle.done()
またはturtle.Screen().exitonclick()
(またはscreen.mainloop()
) を呼び出す必要があります。これがないと、プログラムがすぐに終了し、描画されたタートルが見えなくなってしまいます。 - 解決策
プログラムの最後にturtle.done()
またはscreen = turtle.Screen()
の後にscreen.exitonclick()
を追加してください。
import turtle t1 = turtle.Turtle() t2 = turtle.Turtle() all_turtles = turtle.turtles() for t in all_turtles: t.forward(50) # これがないとすぐにウィンドウが閉じる turtle.done() # または # screen = turtle.Screen() # screen.exitonclick()
- 原因
AttributeError: 'list' object has no attribute '...'
turtle.turtles()
はタートルオブジェクトのリストを返します。このリスト自体はタートルではありません。
考えられる原因と解決策
- リストオブジェクトを直接タートルとして扱おうとしている
- 原因
turtle.turtles()
の戻り値であるリストに対して、直接forward()
やleft()
といったタートルメソッドを呼び出そうとすると、このエラーが発生します。
import turtle t1 = turtle.Turtle() t2 = turtle.Turtle() all_turtles = turtle.turtles() # 間違い: all_turtles はリストなので forward メソッドはない # all_turtles.forward(50)
- 解決策
リストから個々のタートルオブジェクトを取り出して操作する必要があります。通常はfor
ループを使います。
import turtle t1 = turtle.Turtle() t2 = turtle.Turtle() all_turtles = turtle.turtles() for t in all_turtles: # リストの各要素(タートルオブジェクト)をループ処理 t.forward(50) t.left(90) turtle.done()
- 原因
タートルを削除した後も turtle.turtles() に含まれている
clear()
や reset()
はタートルの描画をクリアしたり、状態をリセットしたりしますが、タートルオブジェクト自体を削除するわけではありません。
考えられる原因と解決策
- タートルオブジェクト自体が削除されていない
- 原因
turtle.clear()
やturtle.reset()
は、タートルの画面上の描画を消したり、初期状態に戻したりするだけで、そのタートルオブジェクトがメモリから解放されたり、turtle.turtles()
が返すリストから除外されたりするわけではありません。 - 解決策
特定のタートルオブジェクトを完全に削除したい場合は、そのタートルを参照している変数の参照をなくすか、Pythonのガベージコレクションに任せることになります。しかし、turtle
モジュールが管理するアクティブなタートルのリストから明示的に削除する直接的なメソッドは提供されていません。通常は、一度作成されたタートルオブジェクトは、プログラムが終了するまで存在し続けると考えて問題ありません。もし使わないタートルがあるなら、そのタートルをhideturtle()
で非表示にしたり、画面外に移動させたりするのが一般的です。
- 原因
NameError: name 'turtle' is not defined または NameError: name 'Turtle' is not defined
これは turtle.turtles()
に直接関連するものではありませんが、turtle
モジュールを使用する際の最も基本的なエラーです。
考えられる原因と解決策
- モジュールのインポート忘れ、または間違ったインポート方法
- 原因
turtle
モジュールを使用する前にimport turtle
を行っていなかったり、from turtle import *
のようにワイルドカードインポートをしていないのにTurtle()
やturtles()
を直接呼び出そうとしている場合に発生します。
# 間違い: import turtle がない # t = turtle.Turtle() # 間違い: from turtle import * がないのに Turtle() を呼び出そうとしている # t = Turtle()
- 解決策
正しくモジュールをインポートします。import turtle
を使用する場合:import turtle t = turtle.Turtle() all_turtles = turtle.turtles()
from turtle import *
を使用する場合:
(ただし、名前空間の衝突を避けるため、通常はfrom turtle import * t = Turtle() all_turtles = turtles() # この場合、turtles() も直接呼び出せる
import turtle
を推奨します。)
- 原因
- コードを小分けにして実行する
複雑なプログラム全体を一度に実行するのではなく、タートルを生成する部分、turtle.turtles()
を呼び出す部分、タートルを操作する部分など、小分けにして実行し、それぞれの段階で期待通りの動作をしているか確認します。 - print() を活用する
turtle.turtles()
が実際に何を返しているのか、リストの要素は何なのかを確認するためにprint(all_turtles)
やprint(type(t))
(ループ内で) を使うと、デバッグに役立ちます。
import turtle
import random
# 画面のセットアップ
screen = turtle.Screen()
screen.setup(width=600, height=400)
screen.bgcolor("lightblue")
screen.tracer(0) # 描画を速くするためにアニメーションをオフにする
# 5つのタートルを作成
turtles_list = []
for i in range(5):
t = turtle.Turtle()
t.shape("turtle")
t.color(random.choice(["red", "blue", "green", "purple", "orange"]))
t.penup() # 線を描かずに移動
t.goto(random.randint(-280, 280), random.randint(-180, 180)) # ランダムな位置に移動
t.pendown() # 線を描く
turtles_list.append(t) # 作成したタートルをリストに追加 (このリストは後でturtle.turtles()と比較するため)
# 現在画面上に存在する全てのタートルを取得
all_active_turtles = turtle.turtles()
print(f"作成したタートルの数: {len(turtles_list)}")
print(f"turtle.turtles() で取得したタートルの数: {len(all_active_turtles)}")
# 取得した全てのタートルを動かす
for current_turtle in all_active_turtles:
current_turtle.forward(random.randint(50, 150))
current_turtle.left(random.randint(0, 360)) # ランダムな角度で曲がる
screen.update() # 描画を更新して表示する
screen.exitonclick() # クリックでウィンドウを閉じる
解説
for current_turtle in all_active_turtles:
のループ内で、リストの各タートルにforward()
やleft()
メソッドを適用しています。これにより、全てのタートルが個別に動き回ります。turtle.turtles()
を呼び出すと、これらの現在アクティブなタートルオブジェクトが全てリストとして返されます。turtles_list
に手動で追加したものと同じ内容になっていることが確認できます。turtle.Turtle()
を呼び出すたびに新しいタートルオブジェクトが作成されます。screen.tracer(0)
とscreen.update()
を使うことで、タートルが動く様子を高速に表示できます。これにより、複数のタートルが同時に動くような錯覚を与えられます。
例2: 特定の条件を満たすタートルのみを操作する
turtle.turtles()
で取得したリストを元に、特定の条件(例えば、色や位置)に基づいてタートルを操作する例です。
import turtle
import random
screen = turtle.Screen()
screen.setup(width=700, height=500)
screen.bgcolor("black")
screen.tracer(0)
colors = ["red", "green", "blue", "yellow", "white", "cyan", "magenta"]
# 10個のタートルを作成し、ランダムな色と位置に配置
for _ in range(10):
t = turtle.Turtle("circle") # 形を円にする
t.shapesize(0.5) # サイズを小さくする
t.color(random.choice(colors))
t.penup()
t.goto(random.randint(-300, 300), random.randint(-200, 200))
t.pendown()
# 全てのタートルを取得
all_turtles = turtle.turtles()
print(f"全タートルの数: {len(all_turtles)}")
# 「赤色」のタートルだけを動かす
print("赤色のタートルを動かします...")
for t in all_turtles:
if t.pencolor() == "red": # タートルのペンの色が赤かどうかをチェック
t.right(90)
t.forward(100)
t.write("Red Here!", align="center", font=("Arial", 10, "normal")) # テキストを描画
# 「緑色」のタートルを大きくする
print("緑色のタートルを大きくします...")
for t in all_turtles:
if t.pencolor() == "green":
t.shapesize(2) # サイズを大きくする
t.dot(20, "lime") # 点を描く
screen.update()
screen.exitonclick()
解説
if
文で条件分岐を行うことで、特定のタートル群にのみアクションを実行できます。t.pencolor()
メソッドを使って、タートルの現在のペンの色を取得しています。- この例では、作成されたタートルの中から、色を条件として特定のタートルだけを操作しています。
clear()
や reset()
が turtle.turtles()
のリストの内容を直接変更しないことを示す例です。
import turtle
import time
screen = turtle.Screen()
screen.setup(width=600, height=400)
screen.tracer(0)
# 2つのタートルを作成
t1 = turtle.Turtle("turtle")
t1.color("blue")
t1.penup()
t1.goto(-100, 0)
t1.pendown()
t2 = turtle.Turtle("turtle")
t2.color("red")
t2.penup()
t2.goto(100, 0)
t2.pendown()
screen.update()
print(f"初期状態のタートルの数: {len(turtle.turtles())}") # 2が出力されるはず
time.sleep(1) # 1秒待つ
# t1 に線を引かせる
t1.forward(50)
screen.update()
time.sleep(1)
# t1 の描画をクリアする (タートルオブジェクト自体は残る)
t1.clear()
screen.update()
print(f"t1.clear() 後もタートルの数は変わらない: {len(turtle.turtles())}") # 2のまま
time.sleep(1)
# t2 をリセットする (描画クリアと初期状態に戻る)
t2.reset()
t2.color("green") # 色もリセットされるので再度設定
t2.penup()
t2.goto(0, 0) # 位置もリセットされるので再度設定
t2.pendown()
t2.circle(50)
screen.update()
print(f"t2.reset() 後もタートルの数は変わらない: {len(turtle.turtles())}") # 2のまま
screen.update()
screen.exitonclick()
- つまり、
turtle.turtles()
は現在画面上に存在し、アクティブなタートルオブジェクトの数を数えるものであり、個々のタートルオブジェクトの描画や状態の変更には影響されません。タートルオブジェクトを完全にメモリから解放するには、Pythonのガベージコレクションに任せるしかありませんが、turtle
モジュールを終了させるか、プログラムを終了させることが一般的です。 t2.reset()
は、タートルt2
の描画を消去し、その状態(位置、向き、色など)を初期状態に戻します。しかし、これもタートルオブジェクトt2
自体を削除するわけではないため、turtle.turtles()
の返すリストの長さは変わりません。t1.clear()
はタートルt1
が描いた線のみを消去します。タートルオブジェクトt1
自体は存在し続け、turtle.turtles()
が返すリストにも含まれたままです。
タートルオブジェクトを自分で管理するリストに格納する
これが最も一般的で、推奨される代替方法です。プログラム内でタートルを作成する際に、それらを自分で作成したリストに追加していきます。
メリット
- 初期化時にしか存在しないタートルにもアクセスできる
turtle.turtles()
はその時点で「アクティブ」なタートルを返すため、プログラム開始時に作成したタートルが何らかの理由で内部的に非アクティブになった場合、それらを追跡し続けることができます(稀なケースですが)。 - 柔軟性
特定の条件を満たすタートルのみをリストに追加したり、後からリストから削除したりするなど、より細かな管理が可能です。 - 明示的な管理
どのタートルがリストに含まれているかが明確で、意図しないタートルが混ざる心配がありません。
デメリット
- タートルを作成するたびにリストに追加する手間が発生します。
コード例
import turtle
import random
screen = turtle.Screen()
screen.setup(width=600, height=400)
screen.bgcolor("lightgray")
screen.tracer(0)
my_turtles = [] # 自作のタートルリスト
# 5つのタートルを作成し、自作リストに追加
for i in range(5):
t = turtle.Turtle("turtle")
t.color(random.choice(["red", "blue", "green", "purple", "orange"]))
t.penup()
t.goto(random.randint(-280, 280), random.randint(-180, 180))
t.pendown()
my_turtles.append(t) # ここでリストに追加
print(f"自作リストのタートルの数: {len(my_turtles)}")
print(f"turtle.turtles() で取得したタートルの数: {len(turtle.turtles())}") # 同じ結果になるはず
# 自作リストを使って全てのタートルを動かす
for t in my_turtles:
t.circle(random.randint(20, 80), random.randint(90, 360)) # ランダムな円を描く
screen.update()
screen.exitonclick()
解説
my_turtles = []
で空のリストを作成し、タートルを作成するたびに my_turtles.append(t)
でそのタートルオブジェクトを追加しています。その後の操作は、turtle.turtles()
を使った場合と同様に、この my_turtles
リストに対してループ処理を行います。
クラスとオブジェクト指向プログラミングの活用
複数のタートルがそれぞれ独立した振る舞いや属性を持つ場合、それぞれのタートルを特定のクラスのインスタンスとして定義し、そのクラスのメソッドを使って操作することが考えられます。これは、より複雑なシミュレーションやゲームを作る場合に非常に有効です。
メリット
- 拡張性
新しい種類のタートルを追加する際も、既存のコードに大きな変更を加えることなく対応できます。 - 再利用性
同種のタートルを簡単に複数作成し、同じメソッドで操作できます。 - コードの整理
各タートルのロジックがそのタートル自身のクラス内にカプセル化され、コードが分かりやすくなります。
デメリット
turtle
モジュールに加えて、オブジェクト指向プログラミングの概念(クラス、インスタンス、メソッドなど)の理解が必要です。
コード例
import turtle
import random
class MyMovingTurtle(turtle.Turtle): # turtle.Turtle を継承したクラス
def __init__(self, color, start_x, start_y):
super().__init__() # 親クラス (turtle.Turtle) のコンストラクタを呼び出す
self.shape("turtle")
self.color(color)
self.penup()
self.goto(start_x, start_y)
self.pendown()
self.speed(random.randint(1, 5)) # 各タートルにランダムな速度を設定
def move_randomly(self):
"""ランダムに前進し、曲がる"""
self.forward(random.randint(30, 80))
self.left(random.randint(-180, 180))
screen = turtle.Screen()
screen.setup(width=600, height=400)
screen.bgcolor("black")
screen.tracer(0)
# MyMovingTurtle クラスのインスタンスを作成し、リストに格納
turtles_in_oop = []
colors = ["red", "blue", "green", "yellow", "white"]
for i in range(len(colors)):
x = random.randint(-280, 280)
y = random.randint(-180, 180)
t = MyMovingTurtle(colors[i], x, y)
turtles_in_oop.append(t)
# 全てのカスタムタートルを動かす
# (turtle.turtles() を使ってもよいが、ここでは自作リストを使用)
for _ in range(50): # 50ステップのアニメーション
for current_turtle in turtles_in_oop:
current_turtle.move_randomly()
screen.update()
# time.sleep(0.05) # アニメーションを少し遅くしたい場合
screen.exitonclick()
解説
MyMovingTurtle
というクラスを定義し、turtle.Turtle
を継承しています。これにより、turtle.Turtle
の持つ全ての機能に加え、独自のメソッド(move_randomly
)や初期化処理(__init__
)を追加できます。各タートルは MyMovingTurtle
のインスタンスとして作成され、それらをリストで管理することで、それぞれのタートルの状態を独立して維持し、操作できます。
-
複数の種類のタートル、複雑な相互作用、ゲーム開発など、プログラムの規模が大きくなる場合
クラスとオブジェクト指向プログラミングを活用する方法が、コードの可読性、保守性、拡張性を高める上で非常に有効です。 -
簡単なスクリプトや学習目的の場合
turtle.turtles()
を使うか、あるいはタートルオブジェクトを自分で管理するリストに格納する方法がシンプルで分かりやすいでしょう。