turtle.Vec2Dだけじゃない?Pythonで2次元座標を扱う代替手段を比較

2025-06-06

turtle.Vec2D とは

turtle.Vec2D は、Pythonのタートルグラフィックスライブラリ turtle で使用される、2次元ベクトルを表すためのクラスです。簡単に言えば、X座標とY座標の2つの数値で構成される「点」や「方向」を扱うための便利なツールです。

数学的なベクトルと同様に、位置、移動、方向などを表現するのに使われます。turtle グラフィックスでは、タートルの位置や移動量を扱う際に内部的に利用されており、ユーザーもこれを使ってより複雑なグラフィック操作を行うことができます。

主な特徴とできること

  1. 座標の表現
    Vec2D(x, y) の形式で、X座標とY座標を持つ2次元の点を表現します。 例: v = turtle.Vec2D(100, 50) は、(100, 50) の位置を表すベクトルです。

  2. ベクトルの演算
    数学的なベクトル演算(加算、減算、乗算、除算)をサポートしています。

    • 加算 (+)
      2つのベクトルを足し合わせることができます。 v1 = turtle.Vec2D(10, 20) v2 = turtle.Vec2D(30, 40) v_sum = v1 + v2 # 結果は Vec2D(40, 60) これは、ある点から別の点への移動を表現するのに便利です。
    • 減算 (-)
      2つのベクトルを引くことができます。 v_diff = v2 - v1 # 結果は Vec2D(20, 20) これは、2つの点間の相対的な位置や、ある方向から別の方向への変化を表現するのに便利です。
    • スカラ乗算 (*)
      ベクトルを数値で乗算することで、ベクトルの長さを変更できます。 v_scaled = v1 * 2 # 結果は Vec2D(20, 40) これは、移動量を増減させたり、方向を維持したまま拡大・縮小したりするのに使えます。
    • スカラ除算 (/)
      ベクトルを数値で除算することもできます。 v_div = v1 / 2 # 結果は Vec2D(5.0, 10.0)
  3. タプルのような振る舞い
    Vec2D オブジェクトは、タプル((x, y))のように扱うことができます。例えば、アンパックしたり、インデックスでアクセスしたりできます。 x, y = v print(v[0]) # X座標

  4. タートルメソッドとの連携
    turtle モジュールの一部のメソッドは Vec2D オブジェクトを受け取ります。 例えば、turtle.goto() メソッドは、goto(x, y) の他に goto(Vec2D(x, y)) の形式でも使用できます。 import turtle screen = turtle.Screen() t = turtle.Turtle() target_pos = turtle.Vec2D(100, 100) t.goto(target_pos)

なぜ Vec2D が便利なのか?

  • 数学的な概念の直接的な表現
    ベクトル演算を直接コードに落とし込むことができるため、物理シミュレーションや幾何学的な描画を行う際に役立ちます。
  • 複雑な移動の簡略化
    複数の移動ステップを組み合わせる際、Vec2D の加算や減算を使うことで、最終的な位置計算がより直感的になります。
  • コードの可読性向上
    XとYの個別の変数で管理するよりも、Vec2D オブジェクトとして扱うことで、それが「位置」や「方向」であることを明確にできます。
import turtle

# スクリーンとタートルを作成
screen = turtle.Screen()
t = turtle.Turtle()
t.speed(1) # 遅めに設定

# 原点から開始
t.penup()
t.goto(0, 0)
t.pendown()

# 最初の移動ベクトル
move1 = turtle.Vec2D(50, 0) # 右に50
t.goto(t.pos() + move1) # 現在位置にmove1を足して移動

# 次の移動ベクトル
move2 = turtle.Vec2D(0, 50) # 上に50
t.goto(t.pos() + move2)

# 別の移動ベクトル(斜め)
move3 = turtle.Vec2D(-70, -30) # 左下に70, 30
t.goto(t.pos() + move3)

# Vec2Dを使って最終目標地点を設定
final_destination = turtle.Vec2D(-100, -100)
t.color("red")
t.pensize(3)
t.goto(final_destination) # 直接Vec2Dオブジェクトで移動

# 画面を閉じる準備
screen.exitonclick()


turtle.Vec2D におけるよくあるエラーとトラブルシューティング

TypeError: turtle.Vec2D() takes 1 or 2 arguments (X given)

エラーの内容
Vec2D のコンストラクタに、予期しない数の引数が渡された場合に発生します。特に、単一の数値だけを渡そうとしたり、リストやタプルではないオブジェクトを渡そうとしたりした場合によく見られます。


import turtle

# 誤った例: 単一の数値を渡そうとしている
# v = turtle.Vec2D(100) # TypeError を発生させる

# 誤った例: 3つ以上の数値を渡そうとしている
# v = turtle.Vec2D(10, 20, 30) # TypeError を発生させる

トラブルシューティング
turtle.Vec2D は、次のようにX座標とY座標の2つの引数、または2つの要素を持つイテラブル(タプルやリスト)を1つの引数として受け取ります。

  • 1つのイテラブル(タプルやリスト)で渡す場合
    coords = (100, 50)
    v = turtle.Vec2D(coords) # 正しい
    
    coords_list = [100, 50]
    v = turtle.Vec2D(coords_list) # 正しい
    
    特に、turtle.pos() の戻り値のように、すでに Vec2D オブジェクトや (x, y) のタプルが得られている場合は、そのまま Vec2D() の引数として渡すことができます。
  • 2つの引数で渡す場合
    v = turtle.Vec2D(100, 50) # 正しい
    

TypeError: unsupported operand type(s) for +: 'int' and 'turtle.Vec2D' など、演算子の型エラー

エラーの内容
Vec2D オブジェクトと、Vec2D がサポートしていない型のオブジェクト(例えば、通常の整数や文字列)を演算しようとした場合に発生します。Vec2D は、他の Vec2D オブジェクト、または数値(スカラ)との間で加算、減算、乗算、除算をサポートしています。


import turtle

v = turtle.Vec2D(100, 50)

# 誤った例: Vec2Dと通常の数値を足そうとしている(順番が問題になることも)
# result = 10 + v # TypeError

# 誤った例: Vec2Dと文字列を足そうとしている
# result = v + "hello" # TypeError

トラブルシューティング
Vec2D との演算は、Vec2D 同士か、Vec2D と数値の間で行うようにします。

  • Vec2D 同士の演算
    v1 = turtle.Vec2D(10, 20)
    v2 = turtle.Vec2D(30, 40)
    result = v1 + v2 # 正しい
    

AttributeError: 'tuple' object has no attribute 'some_method'

エラーの内容
turtle.Vec2D オブジェクトだと思ってメソッドを呼び出そうとしたが、実際にはPythonの標準の tuple オブジェクトであった場合に発生します。これは、turtle.pos() などが常に Vec2D オブジェクトを返すわけではない、または Vec2D オブジェクトがどこかでタプルに変換されてしまっている場合に起こりえます。


直接 Vec2D を使わずに (x, y) のタプルを作成して、それに Vec2D のメソッド(例: rotate)を呼び出そうとした場合。

import turtle

# これは通常のタプル
my_coords = (100, 50)

# 誤った例: タプルに対してVec2Dのメソッドを呼び出そうとしている
# rotated_coords = my_coords.rotate(90) # AttributeError

トラブルシューティング
Vec2D の特別なメソッド(例: rotate()angle() など)を使用したい場合は、それが確実に turtle.Vec2D のインスタンスであることを確認してください。タプルから Vec2D に変換することも可能です。

import turtle

my_coords = (100, 50)

# タプルからVec2Dに変換
v = turtle.Vec2D(my_coords)
rotated_v = v.rotate(90) # 正しい
print(rotated_v)

turtle.goto() などで Vec2D を使用する際の混乱

エラーの内容
turtle.goto() メソッドは (x, y) の2つの引数を受け取りますが、Vec2D オブジェクトを1つの引数として渡すこともできます。この使い方を混同するとエラーにつながります。


import turtle

t = turtle.Turtle()

# 誤った例: Vec2DオブジェクトをX座標、Y座標として展開せずに渡そうとしている
# t.goto(turtle.Vec2D(100, 100), None) # TypeError: goto() takes 2 positional arguments but 3 were given

turtle.goto() の引数が (x, y) の形式で、Vec2D オブジェクト自体を x と解釈し、Y座標が None となるため、3つの引数が渡されたと解釈されることがあります。)

トラブルシューティング
turtle.goto() には、(x, y) の形式で2つの引数を渡すか、または1つの Vec2D オブジェクトを渡すか、どちらか明確な方法で記述します。

  • Vec2D オブジェクトとして渡す場合
    pos_vector = turtle.Vec2D(100, 100)
    t.goto(pos_vector) # 正しい
    
  • 2つの引数として渡す場合
    t.goto(100, 100) # 正しい
    

Vec2D の要素へのアクセスに関する誤解

エラーの内容
Vec2D オブジェクトはタプルのサブクラスであるため、インデックス ([0], [1]) でX、Y座標にアクセスできますが、プロパティ (.x, .y) でアクセスしようとするとエラーになります。(ただし、これは turtle.Vec2D の実装によるもので、一部のカスタムVec2Dクラスでは .x.y をサポートしている場合もありますが、標準の turtle.Vec2D はサポートしていません。)


import turtle

v = turtle.Vec2D(100, 50)

# 誤った例: プロパティでアクセスしようとしている
# print(v.x) # AttributeError
# print(v.y) # AttributeError

トラブルシューティング
turtle.Vec2D のX座標とY座標にアクセスするには、タプルと同様にインデックスを使用します。

import turtle

v = turtle.Vec2D(100, 50)
print(v[0]) # X座標 (100)
print(v[1]) # Y座標 (50)

# アンパックも可能
x_coord, y_coord = v
print(f"X: {x_coord}, Y: {y_coord}")
  1. エラーメッセージをよく読む
    Pythonのエラーメッセージは、問題の原因と場所を特定する上で非常に役立ちます。特に TypeErrorAttributeError は、型の不一致や存在しない属性にアクセスしようとしていることを示しています。
  2. 型を確認する
    type() 関数を使って、変数やオブジェクトの型を確認してみましょう。
    import turtle
    v = turtle.Vec2D(10, 20)
    print(type(v)) # <class 'turtle.Vec2D'> と表示されるはず
    
  3. ドキュメントを参照する
    turtle.Vec2D の詳細な動作や引数の要件について、Pythonの公式ドキュメントを確認するのが最も確実です。
  4. 小さなコードで試す
    複雑なコードの中でエラーが発生した場合、問題の箇所と思われる部分だけを抜き出して、非常にシンプルなコードで再現を試みましょう。これにより、他の部分の影響を受けずに問題の特定がしやすくなります。
  5. 変数の名前を確認する
    スペルミスや、Vec2D オブジェクトと通常の数値の変数を混同していないか確認しましょう。


基本的な Vec2D の生成とアクセス

Vec2D オブジェクトを作成し、そのX座標とY座標にアクセスする方法です。

import turtle

# (1) 2つの引数でVec2Dを生成
vec1 = turtle.Vec2D(100, 50)
print(f"vec1: {vec1}")
print(f"vec1のX座標: {vec1[0]}") # インデックス0でX座標にアクセス
print(f"vec1のY座標: {vec1[1]}") # インデックス1でY座標にアクセス

# (2) タプルやリストを1つの引数としてVec2Dを生成
coords_tuple = (200, 150)
vec2 = turtle.Vec2D(coords_tuple)
print(f"vec2: {vec2}")

coords_list = [50, 250]
vec3 = turtle.Vec2D(coords_list)
print(f"vec3: {vec3}")

# (3) Vec2Dをアンパックして個別の変数に代入
x_val, y_val = vec1
print(f"アンパックされたX: {x_val}, Y: {y_val}")

解説

  • x, y = vec のようにアンパックすることも可能です。
  • vec[0] でX座標、vec[1] でY座標にアクセスできます。これはタプルと同じように動作します。
  • turtle.Vec2D(x, y) の形式でベクトルを生成します。

Vec2D の演算(加算、減算、スカラ乗算、除算)

Vec2D は数学的なベクトル演算をサポートしており、タートルの移動計算などに役立ちます。

import turtle

v1 = turtle.Vec2D(50, 30)
v2 = turtle.Vec2D(20, 10)

# (1) ベクトルの加算
v_sum = v1 + v2
print(f"v1 + v2 = {v_sum}") # 結果: Vec2D(70, 40)

# (2) ベクトルの減算
v_diff = v1 - v2
print(f"v1 - v2 = {v_diff}") # 結果: Vec2D(30, 20)

# (3) スカラ乗算(ベクトルを数値で掛ける)
v_scaled_mul = v1 * 2
print(f"v1 * 2 = {v_scaled_mul}") # 結果: Vec2D(100, 60)

# (4) スカラ除算(ベクトルを数値で割る)
v_scaled_div = v1 / 2
print(f"v1 / 2 = {v_scaled_div}") # 結果: Vec2D(25.0, 15.0)

# (5) タートルの位置を使った加算
# screen = turtle.Screen()
# t = turtle.Turtle()
# t.penup()
# t.goto(0, 0)
# t.pendown()
# print(f"現在のタートルの位置: {t.pos()}") # t.pos() もVec2Dを返す

# move_vector = turtle.Vec2D(100, 50)
# t.goto(t.pos() + move_vector) # 現在の位置にベクトルを加算して移動
# print(f"移動後のタートルの位置: {t.pos()}")

# screen.exitonclick()

解説

  • t.pos() メソッドは、タートルの現在の位置を Vec2D オブジェクトとして返します。これと他の Vec2D を加算することで、相対的な移動を簡単に計算できます。
  • +, -, *, / 演算子がオーバーロードされており、直感的にベクトル計算が行えます。

turtle.goto() メソッドでの Vec2D の利用

turtle.goto() は、X, Y座標を2つの引数として受け取りますが、1つの Vec2D オブジェクトを直接渡すこともできます。

import turtle

screen = turtle.Screen()
t = turtle.Turtle()
t.shape("turtle")
t.speed(1) # アニメーションを遅くする

# (1) Vec2Dオブジェクトを使って直接移動
target_pos1 = turtle.Vec2D(100, 100)
t.penup()
t.goto(target_pos1) # Vec2Dオブジェクトを引数として渡す
t.pendown()
t.circle(20)

# (2) 現在位置に相対的な移動(移動ベクトルを加算)
# 最初の移動
move_vector1 = turtle.Vec2D(-50, 0)
t.penup()
t.goto(t.pos() + move_vector1) # 現在位置にベクトルを加算して移動
t.pendown()
t.dot(10, "blue")

# 次の移動(さらに相対的に)
move_vector2 = turtle.Vec2D(0, -50)
t.penup()
t.goto(t.pos() + move_vector2)
t.pendown()
t.dot(10, "green")

# (3) 原点からのオフセットをVec2Dで表現
# タートルを原点に戻す
t.penup()
t.home()
t.pendown()

# 原点からVec2D(50, -50) だけオフセットした位置に移動
offset_from_origin = turtle.Vec2D(50, -50)
t.color("purple")
t.goto(offset_from_origin)
t.dot(10, "purple")


screen.exitonclick()

解説

  • t.pos() を使って現在の位置(Vec2D)を取得し、それに移動したい Vec2D を加算することで、相対的な移動が非常に簡潔に書けます。
  • t.goto(some_vec2d_object) のように、Vec2D オブジェクトを直接 goto の引数として渡すことができます。

Vec2D には、角度や長さ(マグニチュード)を計算したり、回転させたりするためのメソッドも用意されています。

import turtle
import math

screen = turtle.Screen()
t = turtle.Turtle()
t.shape("arrow")
t.speed(3)
t.penup()
t.goto(0, 0)
t.pendown()

# ターゲット位置
target_vec = turtle.Vec2D(100, 100)

# (1) ベクトルの角度 (angle())
# x軸の正の方向からの角度(度数法)
angle_to_target = target_vec.angle()
print(f"ターゲットベクトル ({target_vec}) の角度: {angle_to_target:.2f} 度")

# (2) ベクトルの長さ(マグニチュード, abs() を使う)
# ベクトルの長さは、原点からの距離
length_to_target = abs(target_vec)
print(f"ターゲットベクトルの長さ: {length_to_target:.2f}")

# (3) ベクトルの回転 (rotate())
# ターゲットベクトルを90度反時計回りに回転
rotated_vec = target_vec.rotate(90)
print(f"ターゲットベクトルを90度回転: {rotated_vec}")

# タートルで描画してみる
t.pencolor("blue")
t.goto(target_vec) # ターゲットベクトルを描画
t.write(f"Target ({target_vec[0]},{target_vec[1]})", align="left", font=("Arial", 8, "normal"))

t.penup()
t.goto(0, 0)
t.pendown()
t.pencolor("red")
t.goto(rotated_vec) # 回転後のベクトルを描画
t.write(f"Rotated ({rotated_vec[0]:.0f},{rotated_vec[1]:.0f})", align="right", font=("Arial", 8, "normal"))

# タートルの向きをターゲットベクトルに合わせる
t.penup()
t.home() # 原点に戻る
t.setheading(t.towards(target_vec)) # target_vec の方向に向く
t.pendown()
t.pencolor("green")
t.forward(50) # その方向に50進む

screen.exitonclick()

解説

  • t.towards(x, y) または t.towards(vec2d_object): タートルが指定された点(または Vec2D)の方向に向くように、タートルの向きを設定するのに役立ちます。
  • vec.rotate(degrees): 指定された角度(度数法)だけベクトルを回転させた新しい Vec2D オブジェクトを返します。元のベクトルは変更されません。
  • abs(vec): Vec2D オブジェクトの長さ(原点からの距離、マグニチュード)を返します。
  • vec.angle(): Vec2D オブジェクトがX軸の正の方向となす角度を返します(度数法)。

複数の Vec2D をリストに格納して処理

多数の点を扱う場合、Vec2D のリストを作成すると便利です。

import turtle

screen = turtle.Screen()
t = turtle.Turtle()
t.speed(5)
t.penup()
t.goto(-200, 0)
t.pendown()
t.color("blue")

# Vec2D オブジェクトのリストを作成
points = [
    turtle.Vec2D(-150, 50),
    turtle.Vec2D(-100, -50),
    turtle.Vec2D(-50, 70),
    turtle.Vec2D(0, 0),
    turtle.Vec2D(50, -30),
    turtle.Vec2D(100, 60),
    turtle.Vec2D(150, 10),
]

# リスト内の各点へ移動して描画
for point in points:
    t.goto(point)
    t.dot(5, "red") # 点を打つ

# 全ての点をオフセットする
offset_vector = turtle.Vec2D(0, 100) # Y方向に100ずらす
t.penup()
t.goto(-200, 100) # 描画開始位置もオフセットに合わせて移動
t.pendown()
t.color("green")

for point in points:
    new_point = point + offset_vector # 各点にオフセットベクトルを加算
    t.goto(new_point)
    t.dot(5, "orange")

screen.exitonclick()
  • ループ内で Vec2D の演算を行うことで、全ての点に一括で変換(この場合はオフセット)を適用できます。
  • Vec2D オブジェクトをリストに格納することで、複数の点を効率的に管理できます。


turtle.Vec2D は基本的に (x, y) 座標の組をオブジェクト指向的に扱うためのものです。したがって、代替方法も (x, y) の組をどのように表現・操作するかに焦点を当てます。

タプル (tuple) を直接使う

最もシンプルで直接的な代替方法です。Pythonの組み込み型であるタプルを (x, y) 座標の組として使います。

特徴

  • メモリ効率
    Vec2D オブジェクトよりも軽量です。
  • イミュータブル (Immutable)
    一度作成すると変更できません。これは、座標が意図せず変更されるのを防ぐのに役立つ場合もあれば、不便な場合もあります。
  • シンプルさ
    Pythonの基本的なデータ型なので、特別なインポートやオブジェクト生成の知識が不要です。

演算の代替方法
タプル自体は直接的な数学演算 (+, -, *, /) をサポートしていません。したがって、これらの演算は手動で実装する必要があります。


import turtle

screen = turtle.Screen()
t = turtle.Turtle()
t.speed(1)

# 現在位置の取得 (t.pos() はVec2Dを返すので、ここでは(x,y)タプルとして扱う)
# 厳密には t.pos() は Vec2D を返しますが、ここでは例としてタプルとして扱います。
# 実際にはタプルに変換して操作することになります。
current_pos = (t.xcor(), t.ycor()) # X, Y座標を個別に取得してタプルにする

# (1) 移動先のタプル定義
target_pos_tuple = (100, 50)
t.penup()
t.goto(target_pos_tuple[0], target_pos_tuple[1]) # タプルの要素を個別に渡す
t.pendown()
t.dot(5, "blue")

# (2) 相対的な移動の計算(手動演算)
# 現在のタートル位置
current_x, current_y = t.xcor(), t.ycor()
current_pos_tuple = (current_x, current_y)

# 移動量タプル
move_amount_tuple = (50, -30)

# 新しい位置を計算 (各要素を手動で加算)
new_x = current_pos_tuple[0] + move_amount_tuple[0]
new_y = current_pos_tuple[1] + move_amount_tuple[1]
new_pos_tuple = (new_x, new_y)

t.penup()
t.goto(new_pos_tuple[0], new_pos_tuple[1])
t.pendown()
t.dot(5, "red")

# (3) スカラ乗算の例(手動演算)
vec_tuple = (10, 20)
scale_factor = 3
scaled_vec_tuple = (vec_tuple[0] * scale_factor, vec_tuple[1] * scale_factor)
print(f"スケーリングされたベクトル (タプル): {scaled_vec_tuple}") # (30, 60)

screen.exitonclick()

利点

  • 特別なオブジェクトの概念を導入する必要がない。
  • Pythonの基本的な知識だけで使える。

欠点

  • Vec2D が提供する便利なメソッド(例: angle(), rotate(), abs())が使えない。
  • 可読性が低くなる可能性がある(特に複雑な演算の場合)。
  • 演算が冗長になりがち(x1 + x2, y1 + y2 のように毎回書く必要がある)。

リスト (list) を使う

タプルと同様に、リストを [x, y] 座標の組として使う方法です。

特徴

  • 演算の代替方法
    タプルと同様に、手動で演算を実装する必要があります。
  • ミュータブル (Mutable)
    一度作成した後でも要素を変更できます。これは、座標を頻繁に更新する場合に便利です。


import turtle

screen = turtle.Screen()
t = turtle.Turtle()
t.speed(1)

# 現在位置の取得 (X, Yを個別に取得してリストにする)
current_pos_list = [t.xcor(), t.ycor()]

# (1) 移動先のリスト定義
target_pos_list = [100, 50]
t.penup()
t.goto(target_pos_list[0], target_pos_list[1]) # リストの要素を個別に渡す
t.pendown()
t.dot(5, "blue")

# (2) 相対的な移動の計算(リスト要素の直接更新)
current_pos_list = [t.xcor(), t.ycor()] # 現在位置を更新

# 移動量リスト
move_amount_list = [50, -30]

# 新しい位置を計算し、リストを直接更新
current_pos_list[0] += move_amount_list[0]
current_pos_list[1] += move_amount_list[1]

t.penup()
t.goto(current_pos_list[0], current_pos_list[1])
t.pendown()
t.dot(5, "red")

screen.exitonclick()

利点

  • 要素を直接変更できるため、一部のシナリオでは柔軟性が高い。

欠点

  • 不注意な変更により、意図しないバグが発生する可能性もある(ミュータブルであるため)。
  • タプルと同じく、演算が冗長で可読性が低い。

辞書 (dict) を使う

{'x': x_val, 'y': y_val} のように辞書を使って座標を表現することも可能です。

特徴

  • 可読性
    キー名でアクセスするため、xy が何を表すかが明確になります。

演算の代替方法
辞書の値にアクセスして手動で演算します。


import turtle

screen = turtle.Screen()
t = turtle.Turtle()
t.speed(1)

# (1) 座標を辞書で定義
target_pos_dict = {'x': 100, 'y': 50}
t.penup()
t.goto(target_pos_dict['x'], target_pos_dict['y'])
t.pendown()
t.dot(5, "blue")

# (2) 相対的な移動の計算
current_pos_dict = {'x': t.xcor(), 'y': t.ycor()}
move_amount_dict = {'x': 50, 'y': -30}

# 新しい位置を計算し、辞書を直接更新
current_pos_dict['x'] += move_amount_dict['x']
current_pos_dict['y'] += move_amount_dict['y']

t.penup()
t.goto(current_pos_dict['x'], current_pos_dict['y'])
t.pendown()
t.dot(5, "red")

screen.exitonclick()

利点

  • 関連する他のデータも一緒に管理できる。
  • キー名によって、アクセスする座標がXかYか分かりやすい。

欠点

  • 数学演算がやはり冗長になる。
  • タプルやリストよりもタイプ量が増える。

自作のクラスを定義する

最も turtle.Vec2D に近い代替方法です。自分で2次元ベクトルを表すクラスを定義し、特殊メソッド(__add__, __sub__ など)をオーバーロードすることで、Vec2D と同様の演算子オーバーロードを実現できます。

特徴

  • カスタマイズ性
    必要に応じて追加のメソッド(例: normalize(), dot_product())を実装できます。
  • オブジェクト指向
    コードの構造化と再利用性が向上します。
  • 完全な制御
    Vec2D が提供する機能のすべてを自分で実装できます。


import turtle
import math

class MyVec2D:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    # 文字列表示
    def __repr__(self):
        return f"MyVec2D({self.x}, {self.y})"

    # 加算 (self + other)
    def __add__(self, other):
        if isinstance(other, MyVec2D):
            return MyVec2D(self.x + other.x, self.y + other.y)
        # 数値との加算はここではサポートしない(必要なら追加)
        return NotImplemented

    # 減算 (self - other)
    def __sub__(self, other):
        if isinstance(other, MyVec2D):
            return MyVec2D(self.x - other.x, self.y - other.y)
        return NotImplemented

    # スカラ乗算 (self * factor)
    def __mul__(self, factor):
        if isinstance(factor, (int, float)):
            return MyVec2D(self.x * factor, self.y * factor)
        return NotImplemented

    # スカラ乗算 (factor * self) - 右側の乗算
    def __rmul__(self, factor):
        return self.__mul__(factor)

    # スカラ除算 (self / factor)
    def __truediv__(self, factor):
        if isinstance(factor, (int, float)) and factor != 0:
            return MyVec2D(self.x / factor, self.y / factor)
        return NotImplemented

    # ベクトルの長さ(マグニチュード)
    def length(self):
        return math.sqrt(self.x**2 + self.y**2)

    # 角度(X軸の正の方向から)
    def angle(self):
        return math.degrees(math.atan2(self.y, self.x))

# --- MyVec2D の使用例 ---
screen = turtle.Screen()
t = turtle.Turtle()
t.speed(1)

v1 = MyVec2D(50, 30)
v2 = MyVec2D(20, 10)

# 演算子のオーバーロード
v_sum = v1 + v2
print(f"v1 + v2 = {v_sum}") # MyVec2D(70, 40)

v_scaled = v1 * 2
print(f"v1 * 2 = {v_scaled}") # MyVec2D(100, 60)

print(f"v1の長さ: {v1.length():.2f}")
print(f"v1の角度: {v1.angle():.2f}度")

# タートルでの使用 (goto は MyVec2D オブジェクトを直接受け取らないので、要素を渡す)
t.penup()
t.goto(v_sum.x, v_sum.y)
t.pendown()
t.dot(5, "green")

screen.exitonclick()

利点

  • オブジェクト指向プログラミングの練習になる。
  • コードの構造が明確で、拡張性が高い。
  • turtle.Vec2D と同等か、それ以上の機能を実現できる。
  • turtle.goto(MyVec2D_object) のように直接渡すことはできない(タートルが MyVec2D を認識しないため、t.goto(my_vec.x, my_vec.y) のように個別に要素を渡す必要がある)。
  • 実装に手間がかかる。
代替方法利点欠点turtle.Vec2D と比較して
タプルシンプル、軽量、不変性演算が冗長、ミュータブルな変更不可、ユーティリティメソッドがない最も原始的。可読性と操作性で Vec2D に劣る。
リストシンプル、要素変更可能演算が冗長、不注意な変更のリスク、ユーティリティメソッドがないタプルと似ているが、要素変更が可能。
辞書キーによるアクセスで可読性向上、追加情報可演算が冗長、タイプ量が多い、ユーティリティメソッドがない構造は分かりやすいが、数学的演算には不向き。
自作クラスVec2D と同等かそれ以上の機能、拡張性実装に手間がかかる、turtle メソッドへの直接渡しができない(変換が必要)Vec2D と最も近いが、turtle モジュールとの統合は劣る。