import os
import time
import gymnasium as gym
from gymnasium import spaces
import numpy as np
import pybullet as p
import pybullet_data
class EggCrackEnv(gym.Env):
metadata = {"render_modes": ["human", "rgb_array"], "render_fps": 60}
def __init__(self, render_mode: bool = False):
super().__init__()
self.render_mode = render_mode
self.physics_client = p.connect(p.GUI if render_mode else p.DIRECT)
p.setAdditionalSearchPath(pybullet_data.getDataPath())
# --- アクション/観測空間 ---
# ※ 行動幅を少し広げる例: ±0.02m に変更
self.action_space = spaces.Box(low=-0.02, high=0.02, shape=(2,), dtype=np.float32)
self.observation_space = spaces.Box(
low=np.array([-1.0, -1.0], dtype=np.float32),
high=np.array([1.0, 1.0], dtype=np.float32),
dtype=np.float32,
)
# シミュレーション管理
self.max_steps = 200 # ホライズンを延長
self.step_count = 0
# 報酬設計パラメータ
self.delta_scale = 50.0 # スケールを半分にして学習を安定化
self.break_threshold = 0.12
self.success_bonus = 20.0 # ボーナスを大きめに
self.broken = False
self.prev_dist = 0.0
# URDF ディレクトリ設定
script_dir = os.path.dirname(os.path.abspath(__file__))
project_root = os.path.dirname(script_dir)
self.urdf_dir = os.path.join(project_root, "egg_env")
self._load_environment()
def _load_environment(self):
p.resetSimulation()
p.setGravity(0, 0, -9.8)
p.loadURDF("plane.urdf")
# URDF 読み込み
self.egg_L = p.loadURDF(os.path.join(self.urdf_dir, "egg_L.urdf"), basePosition=[-0.03, 0, 0.05])
self.egg_R = p.loadURDF(os.path.join(self.urdf_dir, "egg_R.urdf"), basePosition=[ 0.03, 0, 0.05])
self.gripper_L = p.loadURDF(os.path.join(self.urdf_dir, "gripper_L.urdf"), basePosition=[-0.05, 0, 0.05])
self.gripper_R = p.loadURDF(os.path.join(self.urdf_dir, "gripper_R.urdf"), basePosition=[ 0.05, 0, 0.05])
# 固定制約
for g, e in [(self.gripper_L, self.egg_L), (self.gripper_R, self.egg_R)]:
p.createConstraint(g, -1, e, -1, p.JOINT_FIXED, [0,0,0], [0,0,0], [0,0,0])
if self.render_mode:
p.resetDebugVisualizerCamera(0.2, 0, -40, [0,0,0.05])
def reset(self, *, seed=None, options=None):
super().reset(seed=seed)
self._load_environment()
self.step_count = 0
self.broken = False
posL = p.getBasePositionAndOrientation(self.egg_L)[0]
posR = p.getBasePositionAndOrientation(self.egg_R)[0]
self.prev_dist = abs(posL[0] - posR[0])
return self._get_obs(), {}
def _get_obs(self):
xL = p.getBasePositionAndOrientation(self.gripper_L)[0][0]
xR = p.getBasePositionAndOrientation(self.gripper_R)[0][0]
return np.array([xL, xR], dtype=np.float32)
def step(self, action):
self.step_count += 1
# —— グリッパー移動
for gripper, dv in [(self.gripper_L, action[0]), (self.gripper_R, action[1])]:
pos = np.array(p.getBasePositionAndOrientation(gripper)[0])
p.resetBasePositionAndOrientation(gripper, pos + [dv, 0, 0], [0,0,0,1])
p.stepSimulation()
if self.render_mode:
time.sleep(1.0 / self.metadata["render_fps"])
# —— 距離と報酬
posL = p.getBasePositionAndOrientation(self.egg_L)[0]
posR = p.getBasePositionAndOrientation(self.egg_R)[0]
dist = abs(posL[0] - posR[0])
delta = dist - self.prev_dist
reward = delta * self.delta_scale
if dist > self.break_threshold and not self.broken:
reward += self.success_bonus
self.broken = True
self.prev_dist = dist
# —— 終了判定
terminated = dist > self.break_threshold
truncated = self.step_count >= self.max_steps
# 成功情報を返す
info = {"is_success": bool(terminated)}
return self._get_obs(), float(reward), terminated, truncated, info
def close(self):
if p.isConnected():
p.disconnect()
インポートと依存ライブラリ
python
コピーする
編集する
import os
import time
import gymnasium as gym
from gymnasium import spaces
import numpy as np
import pybullet as p
import pybullet_data
gymnasium:OpenAI Gym の後継ライブラリ。環境の共通インターフェースを提供します。fileciteturn0file0
spaces:行動空間・観測空間を定義するためのモジュール。fileciteturn0file0
pybullet:物理シミュレータ。ロボットや剛体のシミュレーションを行います。fileciteturn0file0
pybullet_data:標準的な URDF 等のサンプルデータを提供。fileciteturn0file0
2. クラス定義とコンストラクタ (__init__)
python
コピーする
編集する
class EggCrackEnv(gym.Env):
metadata = {“render_modes”: [“human”, “rgb_array”], “render_fps”: 60}
def __init__(self, render_mode: bool = False):
super().__init__()
self.render_mode = render_mode
self.physics_client = p.connect(p.GUI if render_mode else p.DIRECT)
p.setAdditionalSearchPath(pybullet_data.getDataPath())
EggCrackEnv は gym.Env を継承したカスタム環境です。
render_mode=True なら GUI 表示、False ならヘッドレス(DIRECT)モードでシミュレータに接続します。fileciteturn0file0
metadata にレンダリングモードとフレームレートを定義。
3. 行動空間と観測空間の設定
python
コピーする
編集する
self.action_space = spaces.Box(low=-0.02, high=0.02, shape=(2,), dtype=np.float32)
self.observation_space = spaces.Box(
low=np.array([-1.0, -1.0], dtype=np.float32),
high=np.array([1.0, 1.0], dtype=np.float32),
dtype=np.float32,
)
行動空間 (action_space)
左右のグリッパーを x 軸方向に ±0.02m 動かす連続値2次元ベクトル。fileciteturn0file0
観測空間 (observation_space)
左右グリッパーの x 座標をそれぞれ [−1.0, 1.0] の範囲で返す2次元ベクトル。fileciteturn0file0
4. シミュレーション管理パラメータ
python
コピーする
編集する
self.max_steps = 200
self.step_count = 0
self.delta_scale = 50.0
self.break_threshold = 0.12
self.success_bonus = 20.0
self.broken = False
self.prev_dist = 0.0
エピソード最大ステップ数は 200。
1ステップごとに報酬スケール (delta_scale) を掛け、卵が割れた時の閾値 (break_threshold) を超えたら一度だけ成功ボーナス (success_bonus) を加算。fileciteturn0file0
5. URDF ファイルの読み込み (_load_environment)
python
コピーする
編集する
script_dir = os.path.dirname(os.path.abspath(__file__))
project_root = os.path.dirname(script_dir)
self.urdf_dir = os.path.join(project_root, “egg_env”)
プロジェクトルートから egg_env ディレクトリを探し、URDF ファイルをロードします。fileciteturn0file0
python
コピーする
編集する
p.resetSimulation()
p.setGravity(0, 0, -9.8)
p.loadURDF(“plane.urdf”)
…
self.egg_L = p.loadURDF(… “egg_L.urdf”, basePosition=[-0.03, 0, 0.05])
…
p.createConstraint(g, -1, e, -1, p.JOINT_FIXED, …)
地面として平面 URDF、左右の卵 (egg_L, egg_R)、左右のグリッパーをロード。
グリッパーと卵を固定ジョイントでつなぎ、卵を掴んだ状態でシミュレーション。fileciteturn0file0
6. リセット (reset)
python
コピーする
編集する
def reset(self, *, seed=None, options=None):
super().reset(seed=seed)
self._load_environment()
self.step_count = 0
self.broken = False
posL = p.getBasePositionAndOrientation(self.egg_L)[0]
posR = p.getBasePositionAndOrientation(self.egg_R)[0]
self.prev_dist = abs(posL[0] – posR[0])
return self._get_obs(), {}
環境を再構築し、ステップカウント・割れフラグを初期化。
初期状態の左右卵間距離を prev_dist に保存。
観測値(グリッパー位置)を返す。fileciteturn0file0
7. 観測値取得 (_get_obs)
python
コピーする
編集する
def _get_obs(self):
xL = p.getBasePositionAndOrientation(self.gripper_L)[0][0]
xR = p.getBasePositionAndOrientation(self.gripper_R)[0][0]
return np.array([xL, xR], dtype=np.float32)
左右グリッパーの x 座標を NumPy 配列で返却。fileciteturn0file0
8. ステップ実行 (step)
python
コピーする
編集する
def step(self, action):
self.step_count += 1
# グリッパー移動
for gripper, dv in [(self.gripper_L, action[0]), (self.gripper_R, action[1])]:
pos = np.array(p.getBasePositionAndOrientation(gripper)[0])
p.resetBasePositionAndOrientation(gripper, pos + [dv, 0, 0], [0,0,0,1])
p.stepSimulation()
if self.render_mode: time.sleep(1.0 / self.metadata[“render_fps”])
グリッパーを指定量だけ移動
物理シミュレーションを1ステップ実行
レンダリング時はフレームレートに合わせてウェイト
python
コピーする
編集する
# 距離計算と報酬
dist = abs(… new distance …)
delta = dist – self.prev_dist
reward = delta * self.delta_scale
if dist > self.break_threshold and not self.broken:
reward += self.success_bonus
self.broken = True
self.prev_dist = dist
卵同士の距離が広がった分だけ報酬を与え、閾値超過で成功ボーナスを加算。
python
コピーする
編集する
terminated = dist > self.break_threshold
truncated = self.step_count >= self.max_steps
info = {“is_success”: bool(terminated)}
return self._get_obs(), float(reward), terminated, truncated, info
割れ判定(terminated)、ステップ上限判定(truncated)、および成功フラグを info に含めて返す。fileciteturn0file0
9. 終了処理 (close)
python
コピーする
編集する
def close(self):
if p.isConnected():
p.disconnect()
シミュレータ接続を切断してリソースを解放。fileciteturn0file0
以上がこの EggCrackEnv の全体構造と各メソッドの役割です。
学習:エージェントは action_space から左右グリッパーの移動量を出力し、卵を割る(距離を閾値以上に広げる)ことを目指します。
報酬設計:卵が割れるまでの距離変化に応じた逐次報酬と、一度だけ得られる大きなボーナスで学習を促進します。
この構造を理解した上で、例えば報酬設計のパラメータ調整や観測情報の拡張など、自分の目的に合わせたカスタマイズを試してみてください。
importとfromの使い方を教えてください
ChatGPT:
Python で他のモジュール(ファイル)やその中の要素(関数・クラス・変数など)を使うには、主に2つの文法があります:
import モジュール名
from モジュール名 import 名前
以下、使い方と違いを具体例とともに解説します。
1. import モジュール名 の基本
python
コピーする
編集する
import math
print(math.pi) # 3.141592653589793
print(math.sin(0.5)) # 0.4794255386…
モジュール全体を読み込む
モジュール名をプレフィックスとしてアクセス(例:math.〇〇)
名前の衝突を避けやすく、どのモジュールから来た機能か明示できる
エイリアスをつける
長いモジュール名や頻出するモジュールは、短い名前をつけることもできます。
python
コピーする
編集する
import numpy as np
arr = np.array([1, 2, 3])
print(np.mean(arr)) # 2.0
2. from モジュール名 import 名前 の基本
python
コピーする
編集する
from math import pi, sin
print(pi) # 3.141592653589793
print(sin(0.5))# 0.4794255386…
モジュールの一部だけを直接名前空間に読み込む
プレフィックスなしで使えるので、コードが短く書ける
ただし、同じ名前を持つ別モジュールの機能とぶつかるリスクあり
名前にエイリアスをつける
python
コピーする
編集する
from datetime import datetime as dt
now = dt.now()
print(now)
3. from モジュール名 import *(ワイルドカード)
python
コピーする
編集する
from math import *
print(pi)
print(sin(0.5))
モジュール内のすべての公開要素をインポート
推奨されません(どの名前がどこから来たかわかりづらくなるため)
4. パッケージ内の相対インポート
自作のパッケージ構造で、同じパッケージ内の別モジュールを読み込むとき:
markdown
コピーする
編集する
my_package/
├── __init__.py
├── module_a.py
└── subpackage/
├── __init__.py
└── module_b.py
同じ階層:module_b.py から module_a.py を呼ぶ
python
コピーする
編集する
# subpackage/module_b.py
from .. import module_a
同じディレクトリ:
python
コピーする
編集する
# subpackage/module_b.py
from . import helper # subpackage/helper.py がある場合
5. 使い分けのポイント
方式 利点 注意点
import モジュール 名前衝突しにくい、可読性が高い 毎回モジュール名を繰り返す必要がある
import モジュール as 別名 名前を短くできる 多用しすぎると何の省略か迷うことがある
from モジュール import 名前 呼び出しがシンプル 同一名前間の衝突リスク
from モジュール import * コーディング量が最も少ない 何をインポートしたかわからずバグの元に
例:コード構成に応じた使い分け
plaintext
コピーする
編集する
project/
├─ data_loader.py # データ読み込み用
├─ model.py # モデル定義用
└─ train.py # 学習スクリプト
train.py 内では…
python
コピーする
編集する
# model.py の全機能が必要なら
import model
# data_loader.py の load_data 関数だけ使うなら
from data_loader import load_data
# さらに可読性を上げたいなら
from model import M
yModel as M
以上が import と from … import の基本的な使い方です。
プログラムの規模や可読性、名前衝突のリスクを考慮して、適切に使い分けてみてください。
同じ階層:module_b.py から module_a.py を呼ぶ
python
コピーする
編集する
# subpackage/module_b.py
from .. import module_a
同じディレクトリ:
python
コピーする
編集する
# subpackage/module_b.py
from . import helper # subpackage/helper.py がある場合
を詳しく教えてください
Python の相対インポートを正しく使うためには、まず「パッケージ」として認識されるディレクトリ構造を作っておく必要があります。以下の手順で詳しく解説します。
1. パッケージ構造の準備
相対インポートは必ずパッケージの中で行われます。パッケージと認識されるには、各ディレクトリに空の __init__.py(内容は空でも可)を置きます。
markdown
コピーする
編集する
project/
├── __init__.py
├── module_a.py
└── subpackage/
├── __init__.py
├── module_b.py
└── helper.py
project/ がトップレベルパッケージ
subpackage/ がサブパッケージ
それぞれのディレクトリに __init__.py があることで Python はこれらを「パッケージ」と認識します。
2. 同じ階層(兄弟モジュール)からのインポート
subpackage/module_b.py から、親パッケージ直下の module_a.py を呼び出したいケースです。
python
コピーする
編集する
# subpackage/module_b.py
# 1つ上の階層(project/)まで “..” で戻り、
# その階層にある module_a.py を import する
from .. import module_a
def use_a():
# module_a 内の関数やクラスを使う
result = module_a.some_function()
return result
ポイント:
.. は「親ディレクトリ」を指す
その先にあるモジュールやパッケージ名を書けば OK
実行時には、必ず project をトップレベルに含む形で起動(下記 4. を参照)
3. 同じディレクトリ内(同一サブパッケージ)のインポート
同じ subpackage/ 内の helper.py を読み込みたい場合:
python
コピーする
編集する
# subpackage/module_b.py
# “.” は「同じディレクトリ」を指す
from . import helper
def use_helper():
# helper 内の関数を呼び出し
data = helper.load_data()
return data
ポイント:
. は「カレントパッケージ(subpackage)」を表現
helper.py の他にサブパッケージ内のフォルダをさらにたどる場合は . を重ねます(例:from .subsub import foo)
4. 実行方法と common pitfalls
4.1 スクリプト直起動の注意
bash
コピーする
編集する
$ python subpackage/module_b.py
のように直接ファイルを指定して実行すると、Python はそのファイルをトップレベルモジュールとみなしてしまい、相対インポートが失敗します。
正しい実行例(トップレベルを指定):
bash
コピーする
編集する
# project ディレクトリの外、もしくは project/ を PYTHONPATH に入れて…
$ cd project
$ python -m subpackage.module_b
-m パッケージ.モジュール 形式で呼び出すと、パッケージ構造を保ったまま起動できます。
4.2 よくあるエラー
ImportError: attempted relative import beyond top-level package
→ .. が多すぎたり、そもそもパッケージと認識されていない場合
ModuleNotFoundError: No module named ‘project’
→ 実行時のカレントディレクトリや PYTHONPATH が正しくない場合
まとめ
すべてのパッケージディレクトリに __init__.py を配置
相対位置を . や .. で正しく表現
python -m でパッケージとして起動
この3点を守れば、モジュール間の依存をすっきり整理しつつ、柔軟にインポートができます。
相対インポート、パッケージ、トップレベルモジュールについて教えてください
ChatGPT:
以下では、Python のモジュール/パッケージ構造と相対インポートの仕組み、そして「トップレベルモジュール」とは何かをまとめて解説します。
1. モジュールとパッケージ
モジュール(module)
ファイル単位でのコードのまとまり。たとえば foo.py はモジュール foo。
他のモジュールからは import foo で読み込める。
パッケージ(package)
ディレクトリ単位でのモジュールのまとまり。ディレクトリ内に必ず __init__.py(空ファイルで OK)を置くことで、そのフォルダを「パッケージ」として認識させる。
例:
markdown
コピーする
編集する
project/
├─ __init__.py ← これがあると project/ はパッケージ
├─ module_a.py ← project.module_a
└─ subpkg/
├─ __init__.py ← project.subpkg はサブパッケージ
└─ module_b.py ← project.subpkg.module_b
2. トップレベルモジュール/パッケージ
トップレベルとは、あなたがスクリプトを実行するときの「最上位のパッケージ階層」を指します。
たとえば上記構成で、project/ の外部から
bash
コピーする
編集する
$ cd /path/to/project_parent
$ python -m project.subpkg.module_b
と実行すれば、project が「トップレベルパッケージ」になります。
一方で
bash
コピーする
編集する
$ cd project/subpkg
$ python module_b.py
と直接ファイルを叩くと、Python から見るとそのファイルが「トップレベルモジュール」扱いとなり、相対インポートは動きません。
3. 相対インポート
相対インポートは、自分のいるパッケージ階層からの相対位置をドットで表現します。
表現 意味 例
. 同じディレクトリ(同一パッケージ) from . import helper
.. ひとつ上のディレクトリ(親パッケージ) from .. import module_a
… 親のさらに親へ(必要に応じてドットを増やす) from …common import utils
具体例
markdown
コピーする
編集する
project/
├─ __init__.py
├─ module_a.py ← project.module_a
└─ subpkg/
├─ __init__.py
├─ helper.py ← project.subpkg.helper
└─ module_b.py ← project.subpkg.module_b
同じディレクトリ内 のインポート
python
コピーする
編集する
# project/subpkg/module_b.py
from . import helper
# → project.subpkg.helper にアクセス
helper.do_something()
親パッケージ直下 のモジュールを読む
python
コピーする
編集する
# project/subpkg/module_b.py
from .. import module_a
# → project.module_a にアクセス
module_a.some_function()
4. 実行方法のポイント
必ずトップレベルを含む形で起動する:
bash
コピーする
編集する
# project/ の外、または project_parent/ に移動して
$ python -m project.subpkg.module_b
これにより、Python は project をパッケージのルートと認識し、ドット表記で階層構造を解決できます。
逆に、python module_b.py のように直接ファイルを起動すると相対インポートは失敗します。
まとめ
モジュール は単一の .py ファイル、パッケージ はディレクトリ+__init__.py。
トップレベルパッケージ を意識して、python -m パッケージ.モジュール 形式で起動する。
相対インポート(. や ..)はパッケージ階層内部でのみ動作し、自分の位置から「同じ場所」「親の場所」を指し示す。
これらを押さえれば、大規模プロジェクトでも綺麗にモジュール分割・参照ができるようになります。
あなた:
python .\scripts\evaluate_egg_crack.py
のような実行の仕方はよいですか?直接たたくのはよいですか?
ChatGPT:
思考時間: 数秒
直接スクリプトを叩く方法(
bash
コピーする
編集する
python .\scripts\evaluate_egg_crack.py
)にも利点と注意点があります。以下のポイントをご確認ください。
1. 直接実行できるケース
スクリプト内で 同じフォルダ内 のモジュールしか import しておらず、かつ相対インポート(. や ..)を使っていない
sys.path にスクリプトのあるディレクトリが自動的に追加されるので、同じ階層のモジュールがすんなり読める
このような単一ファイルのスクリプトや、“スクリプト+同階層モジュール” の小規模プロジェクトであれば、直接叩いて問題ありません。
2. 直接実行が NG になるケース
パッケージ階層を維持した 相対インポート(from .. import module_a や from .helper import foo)を使っている
スクリプトがプロジェクトルートより深いディレクトリ(たとえば project/scripts/evaluate_egg_crack.py)にあって、ルートの他モジュールを相対パスで呼び出している
直接ファイルを実行すると、そのスクリプト自身が「トップレベルモジュール」とみなされてしまい、相対インポートの基準(パッケージ構造)が失われ、ImportError になります。
3. 安全な実行方法
パッケージ構造を保ったまま起動するには、プロジェクトのルートを Python のトップレベルパッケージとして認識させる必要があります。具体的には、
bash
コピーする
編集する
# プロジェクトルートに移動して
cd path/to/egg_cracker_rl
# -m オプションでパッケージ名を指定
python -m scripts.evaluate_egg_crack
とすると、
Python は egg_cracker_rl/ をパッケージのルートとして扱い、
scripts/evaluate_egg_crack.py の中で書かれた相対インポートも問題なく解決する
というメリットがあります。
4. まとめ
単体スクリプト かつ 相対インポートを使っていない 場合は、python .\scripts\evaluate_egg_crack.py で OK。
相対インポートを含む パッケージ構成の場合は、必ず python -m パッケージ.モジュール 形式で呼び出すのがベストプラクティスです。
開発/デプロイのどちらでも安定させたい場合は、後者(-m を使う方法)を標準化しておくと安心です。
python -m パッケージ.モジュールとした場合、現在いるディレクトリがトップレベルモジュールになりますか?
ChatGPT:
思考時間: 7 秒
「python -m package.module」で起動するときの挙動は次のようになります。
カレントディレクトリがモジュール検索パスに追加される
実際には、Python は起動時に現在の作業ディレクトリ(=カレントディレクトリ)を sys.path[0] に挿入します。
これにより、そのディレクトリ配下にある package フォルダを「トップレベルのパッケージ」として認識できるようになる、というイメージです。
「トップレベルモジュール」とは何か
「トップレベルモジュール」という言い回しは少し混乱を招きますが、要するに――
スクリプトを直接 python some_script.py で実行した場合、some_script 自身がトップレベルモジュール扱いになります。
一方で python -m package.module とすると、Python は package.module を名前空間の階層として扱い、module はあくまでそのサブモジュール(完全修飾名:package.module)としてロードします。
つまり、「カレントディレクトリがトップレベルモジュールに変わる」わけではなく、「カレントディレクトリがモジュール検索のルート(トップレベルパッケージの親ディレクトリとして)になる」 という挙動です。
相対インポートが動く理由
-m package.module で起動すると、package は「ちゃんとしたパッケージ」として解釈されるので、package.sub や相対インポート(from . import foo、from ..bar import baz など)が正しく解決されます。
逆に直接 python scripts/evaluate.py と叩くと、そのファイル自体がトップレベルと見なされ、相対パッケージ階層の構造情報が落ちてしまい、相対インポートがエラーになります。
まとめ
カレントディレクトリ=トップレベルパッケージの「親ディレクトリ」 として扱われる。
実際にロードされるトップレベルモジュール名は package であって、カレントディレクトリ自身ではない。
相対インポートを正しく機能させたいなら、必ず python -m package.module 形式で、パッケージ構造を壊さずに起動しましょう。
あなた:
どんどん下の階層にあるpython -m パッケージ.パッケージ.パッケージ.モジュールto
ChatGPT:
思考時間: 4 秒
はい。python -m はドット区切りでいくらでも深い階層まで指定できます。ポイントは以下のとおりです。
パッケージ構造を正しく用意する
各階層のディレクトリに必ず __init__.py(内容は空でも可)を置いて「パッケージ」として認識させる。
例:
markdown
コピーする
編集する
my_project/
├── __init__.py
├── level1/
│ ├── __init__.py
│ ├── level2/
│ │ ├── __init__.py
│ │ └── level3/
│ │ ├── __init__.py
│ │ └── deep_module.py
│ └── other.py
└── main.py
起動コマンド例
bash
コピーする
編集する
cd path/to/my_project_parent # my_project の親ディレクトリに移動
python -m my_project.level1.level2.level3.deep_module
これで deep_module.py の中の
python
コピーする
編集する
if __name__ == “__main__”:
# ここが起動時に実行される部分
…
が呼び出されます。
動作の仕組み
カレントディレクトリ(my_project_parent)が sys.path の先頭に入り、my_project をトップレベルパッケージとして認識。
Python はドットでつながった階層を順にたどり、最後の要素をモジュール(.py ファイル)としてロードします。
注意点
相対インポート を使っている場合も、階層構造が保たれるのでそのまま動きます。
直接 python deep_module.py すると、パッケージ階層が崩れて相対インポートが失敗しやすいので、深い階層ほど -m で起動するのがおすすめです。
まとめると、深い階層のモジュールでも
bash
コピーする
編集する
python -m パッケージ1.パッケージ2.パッケージ3.モジュール名
とすれば問題なく起動できます。
import os
import time
import gymnasium as gym
from gymnasium import spaces
import numpy as np
import pybullet as p
import pybullet_data
読み込んだ、ライブラリは何ができますか?
ChatGPT:
以下の各ライブラリは、それぞれ次のような機能を提供します。
os
ファイル/ディレクトリ操作(パスの結合や存在確認、作成・削除など)
環境変数の取得・設定
実行中のプロセス情報取得(カレントディレクトリ、ファイル名取得など)
time
処理の経過時間計測(time.time() や time.perf_counter())
スリープ(time.sleep(sec))によるウェイト制御
実行時刻の取得(タイムスタンプや構造化時刻への変換)
gymnasium (旧 gym)
強化学習向けの 環境インターフェース を提供
Env クラスを継承して、観測・行動・報酬・終了判定などを統一的に扱える
reset(), step(), render() といった一連のメソッド呼び出しで学習ループを簡潔に記述可能
gymnasium.spaces
観測空間(Box, Discrete, MultiBinary など)と
行動空間(連続/離散/マルチ次元など)を定義し、
エージェントが取りうる行動や受け取る観測を厳密に規格化できる
numpy (np)
多次元配列(ndarray)の高速演算ライブラリ
線形代数、統計、乱数生成、配列変形、論理演算など科学技術計算に必須のツール群
Python のリストに比べ大規模データの処理が高速かつメモリ効率的
pybullet (p)
オープンソースのリアルタイム物理シミュレータ
剛体力学、衝突判定、ジョイント・モーター制御、センサーシミュレーションなど
ロボットアームや四足歩行ロボット、自動運転車などの挙動を物理的に忠実に再現可能
pybullet_data
PyBullet 標準の URDF/SMPL/OBJ モデルやテクスチャ、サンプル環境のデータパスを提供
pybullet_data.getDataPath() で簡単に登録済みのサンプルファイルを読み込めるようにするユーティリティ
これらを組み合わせることで、
OS 上のファイルから URDF モデルを読み込み、
PyBullet 物理エンジンで「卵を掴んで割る」環境を構築し、
Gym のインターフェース(reset, step, render)で強化学習エージェントとやり取りし、
NumPy で観測データや報酬計算を高速に行う
といった一連のシミュレーション・学習ループが構築できます。
classやdefについて教えてください。
Python において、def と class はそれぞれ「関数」を定義するときと「クラス(オブジェクトの設計図)」を定義するときに使うキーワードです。以下、それぞれの役割と基本構文、使いどころを解説します。
1. def:関数(Function)の定義
1.1 基本構文
python
コピーする
編集する
def 関数名(引数1, 引数2, …):
“””(省略可)関数の説明を書くドックストリング”””
# 本体(処理内容)
処理1
処理2
…
return 戻り値 # 省略すると None を返す
1.2 例:2つの数値を足す関数
python
コピーする
編集する
def add(a, b):
“””a と b を足して結果を返す”””
return a + b
result = add(3, 5) # result は 8
1.3 ポイント
引数:関数に渡す値。位置引数のほか、キーワード引数やデフォルト値も指定可能。
ドックストリング:三重引用符文字列で書く関数の説明。help() や自動ドキュメント生成で利用される。
return:呼び出し元に返す値。書かない場合は None を返す。
2. class:クラス(Class)の定義
2.1 基本構文
python
コピーする
編集する
class クラス名(継承元クラス1, 継承元クラス2, …):
“””(省略可)クラスの説明を書くドックストリング”””
# コンストラクタ(インスタンス生成時に呼ばれる初期化メソッド)
def __init__(self, 引数1, 引数2, …):
# インスタンス属性の初期化
self.属性1 = 引数1
self.属性2 = 引数2
# メソッド(クラス内で定義する関数)
def メソッド名(self, 引数…):
“””メソッドの説明”””
処理
return 戻り値
2.2 例:簡単な Dog クラス
python
コピーする
編集する
class Dog:
“””犬を表すクラス”””
def __init__(self, name, age):
# インスタンス生成時に名前と年齢をセット
self.name = name
self.age = age
def bark(self):
“””ワンと鳴く”””
print(f”{self.name} says: ワン!”)
def birthday(self):
“””年齢を 1 歳増やす”””
self.age += 1
print(f”Happy Birthday, {self.name}! You are now {self.age} years old.”)
# インスタンス化(オブジェクトの生成)
puchi = Dog(“プチ”, 2)
puchi.bark() # プチ says: ワン!
puchi.birthday() # Happy Birthday, プチ! You are now 3 years old.
2.3 ポイント
__init__:インスタンス生成時に必ず呼ばれる特殊メソッド。属性を初期化する。
self:インスタンス自身への参照。メソッド定義時は必ず最初の引数に書く。
メソッド:クラス内に定義した関数。第1引数は必ず self。
継承:class 子クラス(親クラス): の形で、親クラスの機能を受け継いで拡張できる。
3. 関数 vs クラス の使い分け
機能 def(関数) class(クラス)
何を定義するか 一連の処理をまとめたブロック 属性と振る舞い(メソッド)をまとめた設計図
状態(データ)管理 引数/返り値でやりとり インスタンス属性として内部に保持
利用シーン 単純に処理を使い回したいとき データと振る舞いを一まとまりに扱いたいとき
関数 は「入力→処理→出力」の流れをシンプルに定義したい場合に用います。
クラス は「あるデータ構造(状態)と、それに紐づく操作(振る舞い)をまとめて扱いたい」場合に適しています。
これで def と class の基本的な使い方と違いがつかめるはずです。さらに詳しく知りたいポイントがあれば、ぜひご質問ください!
defとreturnはセットですか?
def と return はペアで使うことが多いですが、必ずセットというわけではありません。
1. def の役割
def は「関数を定義する」ためのキーワード。
関数内に書いた処理(本体)が実行できるようになります。
python
コピーする
編集する
def greet(name):
print(f”こんにちは、{name}さん!”)
この例では返り値(戻り値)は定義していません。
2. return の役割
return は「呼び出し元に値を返す」ためのキーワード。
return を書かない関数は、呼び出した側には自動的に None を返します。
python
コピーする
編集する
def add(a, b):
return a + b # 計算結果を呼び出し元に返す
def shout(text):
print(text.upper())
# ここに return がない→戻り値は None
python
コピーする
編集する
x = add(2, 3) # x == 5
y = shout(“hi”) # hi と表示され、y == None
3. return が不要なケース
関数の目的が「何かを処理する」「副作用を起こす」(例:画面に出力、ファイル書き込み、画面描画、状態変更など)場合は、戻り値が不要なことも多いです。
Python では、戻り値が不要なら return を書かずに関数本体だけを実装して構いません。
4. まとめ
def:関数を定義する
return:関数の実行結果(値)を呼び出し元に渡す
セットではない:値を返す必要がなければ return を書かずに def だけで完結できます
必要に応じて、戻り値を返すかどうかを設計してください。
インスタンス生成時とはなんですか?
「インスタンス生成時(instantiation)」とは、クラスをもとに「オブジェクト」を実際に作り出すタイミングのことを指します。以下、順を追って説明します。
1. クラスとインスタンスの関係
クラス(class)
オブジェクトの設計図や型を定義したもの。どんな属性(データ)やメソッド(振る舞い)を持つかを定義します。
インスタンス(instance)
その設計図をもとにメモリ上に具体的に生成された「モノ」。クラスを具現化した実体です。
2. インスタンス生成の流れ
python
コピーする
編集する
class Dog:
def __init__(self, name):
# インスタンス生成時に呼ばれる初期化メソッド
self.name = name
# ←ここがインスタンス生成時
puppy = Dog(“ポチ”)
Dog(“ポチ”) の呼び出し
Dog というクラスを「関数呼び出し」のように書くと、Python は内部的に Dog.__new__() → Dog.__init__() という流れでオブジェクトを作ります。
__new__()
実際のメモリ領域を確保して、空のオブジェクトを生成します。
ほとんどの場合、自分でオーバーライドする必要はありません。
__init__()(初期化メソッド)の実行
先ほど確保されたオブジェクトに対して、引数で渡された初期値(ここでは “ポチ”)を使って属性をセットします。
これが「インスタンス生成時に呼ばれる処理」です。
返り値としてインスタンスが返される
最終的に変数 puppy に、name 属性が “ポチ” にセットされた Dog のオブジェクト(インスタンス)が代入されます。
3. なぜ「インスタンス生成時」が重要か
属性の初期化
インスタンスごとに持たせたいデータ(例:self.name、self.age など)をここでセットします。
リソースの確保
ファイルハンドル、外部接続、センサーデバイスなど、オブジェクト生成時に必要なリソースを開く処理を入れることもあります。
不正な値のチェック
引数の検証や例外処理を入れて、安全にインスタンスを生成できるようにガードをかけるタイミングでもあります。
4. ポイントまとめ
インスタンス生成(instantiation)= クラス呼び出し(ClassName(…))
生成プロセスは内部で __new__() → __init__() の順で実行
__init__() が「インスタンス生成時に呼ばれるメソッド」 で、属性初期化やリソース確保、引数チェックを行う
これを理解することで、クラス設計やオブジェクト初期化のタイミングを適切にコントロールできます。
もしさらに詳しく「__new__ と __init__ の違い」や「クラス変数とインスタンス変数の使い分け」について知りたい場合は、お気軽にお尋ねください!
metadata = {“render_modes”: [“human”, “rgb_array”], “render_fps”: 60}
とはどういう操作になりますか?
metadata は Gym 環境(gym.Env)のクラス属性として定義され、主に以下の2つの情報を持たせるために使われます。
python
コピーする
編集する
metadata = {
“render_modes”: [“human”, “rgb_array”],
“render_fps”: 60
}
1. “render_modes”
環境がサポートする レンダリングモード の一覧を列挙します。
例では
“human”:画面にウィンドウを開いてリアルタイムに表示するモード
“rgb_array”:レンダリング結果を NumPy 配列(RGB 画像)として返すモード
ユーザーは環境生成時にどちらかのモードを選べます。
python
コピーする
編集する
env = EggCrackEnv(render_mode=”rgb_array”)
render_mode 引数に対して許可された選択肢を自動的にチェックしてくれる仕組みがあります。
2. “render_fps”
描画のフレームレート(Frames Per Second) を環境側で指定するための値です。
たとえば render_fps=60 としておくと、
python
コピーする
編集する
if self.render_mode == “human”:
time.sleep(1.0 / self.metadata[“render_fps”])
のように1フレームごとに 1/60 秒のウェイトを入れ、60FPS 相当の速度でアニメーションを進めることができます。
これにより、物理シミュレーションと可視化のスピードを調整し、実時間に近い動きを実現します。
まとめ
metadata[“render_modes”] = この環境で使える描画方法のリスト
metadata[“render_fps”] = 人間が見て自然な速度になるように合わせた描画レート
Gym の標準化インターフェースに則って、ユーザーが柔軟に可視化モードを選択しつつ、一定のフレームレートでレンダリングできるようにするための設定、というわけです。
super().__init__()
はどういう処理がされますか?
super().__init__() は、自分(派生クラス) から見て 親クラス(基底クラス)の同名メソッドを呼び出す仕組みです。具体的には以下のような役割があります。
親クラスの初期化処理を実行
python
コピーする
編集する
class EggCrackEnv(gym.Env):
def __init__(self, render_mode=False):
super().__init__()
…
ここでの super().__init__() は、gym.Env(EggCrackEnv の親クラス)の __init__ メソッドを呼び出します。
gym.Env.__init__() の中では、環境の内部的な状態(シード管理用の変数や、各種フラグ初期化など)がセットアップされています。
これを呼ばないと、親クラスが期待する内部構造が未初期化のまま残り、reset() や step() の呼び出し時にエラーになる可能性があります。
メソッド解決順序(MRO)に基づく呼び出し
super() は単に「親クラス」を指すのではなく、メソッド解決順序(MRO)に沿って次に定義されているクラスのメソッドを呼び出します。
たとえば多重継承のクラス階層でも、正しい順序で各クラスの __init__ を順番に実行できるようになります。
コードの重複を避ける
親クラスで既に定義されている初期化ロジックを再実装する必要がなく、親の変更にも自動で追従できます。
その上で派生クラス固有の初期化(パラメータ設定、インスタンス変数の追加など)を、super().__init__() のあとに書き足せます。
図解イメージ
plaintext
コピーする
編集する
┌─────────────────────────┐
│ gym.Env.__init__ │← 内部状態を初期化
└─────▲───────────────────┘
│ super().__init__()
┌─────┴───────────────────┐
│ EggCrackEnv.__init__ │← 自分固有の初期化処理
└─────────────────────────┘
EggCrackEnv.__init__() が呼ばれる
その冒頭で super().__init__() → gym.Env.__init__() を実行
親クラスの初期化が終わったあと、自クラス特有の設定へと処理が戻る
この仕組みによって、ライブラリ側で用意された基本的な初期化ロジックを再利用しつつ、自分の環境に必要な追加設定だけを安全に書くことができるのです。
self.やp.などの書き方や意味について教えてください。
Python での「.(ドット)」は、「名前空間」(namespace)や「オブジェクト」の中にある属性やメソッドを参照するときに使う演算子です。以下、よく出てくるパターンをまとめます。
1. self.
クラス定義内のメソッドで、自分自身(そのメソッドを呼び出されたインスタンス)を指す予約語 self。
python
コピーする
編集する
class MyClass:
def __init__(self, value):
# self.value は「このインスタンスが持つ属性 value」
self.value = value
def double(self):
# self.value にアクセスして、そのインスタンス固有のデータを使う
return self.value * 2
self.value は「このインスタンスが持っている属性 value」
self.method() は「このインスタンスが持つメソッド method を呼び出す」
ポイント
クラスのメソッド定義では、必ず最初の引数として self(任意の名前可ですが慣習的に self)を書きます。
self 経由で属性(データ)や他のメソッドを参照・変更できます。
2. p.(モジュールエイリアス/オブジェクト)
スクリプトの先頭で
python
コピーする
編集する
import pybullet as p
のように、長いモジュール名を短くしておくときによく使われます。
p.connect(…) は pybullet.connect(…) と同じ呼び出し。
p.stepSimulation() は PyBullet のシミュレーションステップを進める関数を呼んでいます。
ポイント
import X as p と書くと、以降は p.some_function() のように短く書けます。
モジュールの中に定義された関数・クラス・定数を呼び出す際に「モジュール名.名前」の構文を使います。
3. 一般的なドット表記パターン
書き方 意味
module.attr モジュール module の中にある属性(関数・クラス・定数)
instance.attr インスタンスが持つ属性(データ)
instance.method() インスタンスのメソッド(振る舞い)を呼び出す
Class.attr クラス変数やクラスメソッドを参照
package.module パッケージ内のサブモジュールを参照
例:複数の使い分け
python
コピーする
編集する
import numpy as np # モジュールエイリアス
from datetime import datetime
class User:
default_role = “guest” # クラス変数
def __init__(self, name):
self.name = name # インスタンス変数
self.created = datetime.now() # モジュール内の関数
def summarize(self):
# インスタンス変数とクラス変数を組み合わせて返す
return f”{self.name} ({self.default_role}) created at {self.created}”
# numpy の関数を使って配列演算
arr = np.array([1, 2, 3])
print(np.mean(arr))
まとめ
.(ドット) は「この中にある○○」を取り出すアクセス演算子
self. は「このインスタンス自身の属性・メソッド」を指す
p.(や np. など)は「そのモジュールの中の○○」を短く呼ぶためのエイリアス表記
ドット表記に慣れると、Python の名前空間やオブジェクト指向のコードがより読みやすく・書きやすくなります。ぜひ色々なコードを追いかけながら使い方を確認してみてください!
def __init__(self, name):
self.name = name # インスタンス変数
self.created = datetime.now() # モジュール内の関数
ではnameはdef __init__(self, name)で扱われていますが、createdはなにもされていないのですが、使ってよいのですか?createdにdatetimeが代入されるのですか?
はい。name と created はそれぞれ役割が違いますが、どちらも インスタンス変数(属性)として self にセットされています。
python
コピーする
編集する
class User:
def __init__(self, name):
self.name = name # 引数 name から設定
self.created = datetime.now() # 引数ではなく、関数の戻り値を設定
self.name = name
こちらは __init__ の引数 name を受け取って、そのままインスタンス変数 name に格納しています。
self.created = datetime.now()
こちらは引数を使うわけではなく、datetime.now() という関数呼び出し(モジュール内のクラス・メソッド)を実行した結果をインスタンス変数 created に格納しています。
ポイントまとめ
引数あり → 属性にセット
python
コピーする
編集する
def __init__(self, foo):
self.foo = foo
関数呼び出し時に渡された値を保持したい場合。
引数なし → 式(関数呼び出しなど)の結果をセット
python
コピーする
編集する
def __init__(self):
self.timestamp = time.time()
self.created = datetime.now()
インスタンス生成のタイミングで自動的に決めたい値(現在時刻、乱数、設定ファイルの読み込み結果など)を保持したい場合。
したがって、created は「引数ではないけれど、このクラスのインスタンスを作るときに必ず持たせたい情報」として、__init__ 内で自由に計算・呼び出しした結果を代入している、というわけです。問題なく使っていただけます。
posR = p.getBasePositionAndOrientation(self.egg_R)[0]
self.prev_dist = abs(posL[0] – posR[0])
selfを使った変数名にするのと、selfを使わないのとの使い訳はなんですか?何がかわりますか?
Python のメソッド内で変数を
python
コピーする
編集する
posR = p.getBasePositionAndOrientation(self.egg_R)[0]
self.prev_dist = abs(posL[0] – posR[0])
のように書くとき、以下の2種類の変数の扱いがあります。
1. ローカル変数(posR や posL のように self. を付けないもの)
スコープ:定義された関数(メソッド)の内部だけで生きている。
ライフタイム:メソッドが返ってくると同時に消える。
用途:そのメソッド内だけで一時的に必要な計算結果や中間値を保持したいとき。
python
コピーする
編集する
def reset(self):
posL = p.getBasePositionAndOrientation(self.egg_L)[0] # ローカル
posR = p.getBasePositionAndOrientation(self.egg_R)[0] # ローカル
# ここだけで使う距離計算
local_dist = abs(posL[0] – posR[0])
# local_dist はこの reset() の中だけで有効
2. インスタンス変数(self.prev_dist のように self. を付けたもの)
スコープ:その インスタンス(self)の中でグローバルにアクセス可能。
ライフタイム:インスタンスが生きている限り維持される。メソッドをまたいで値を保持できる。
用途:エピソードを通じて環境の「状態」として追跡・参照したいとき。
python
コピーする
編集する
def reset(self):
posL = …
posR = …
# エピソード初期状態として self.prev_dist に保存
self.prev_dist = abs(posL[0] – posR[0])
def step(self, action):
# 前ステップの距離を比較するために、self.prev_dist を読み出す
dist = …
delta = dist – self.prev_dist
# 報酬の計算 etc.
# 更新
self.prev_dist = dist
まとめ
ローカル変数 インスタンス変数 (self.付き)
生存期間 メソッドが終わるまで オブジェクトが削除されるまで
スコープ そのメソッド内のみ そのインスタンス内の全メソッドから参照可
用途 一時的な計算・中間データ 環境の「状態」を保持し、メソッド間で共有
prev_dist を次の step() で使いまわす必要があるため、インスタンス変数として self.prev_dist に保存しているわけです。逆に一時的にしか使わない posL や posR はローカル変数にしてメソッドが終わると破棄されるようにしています。
selfをいれなくてもreturnを使えば同じようなことになりますか?
self.で属性としてデータを保持する方法と、関数の戻り値 (return) を使ってデータを渡す方法は、一見似ていますが、役割も動作も大きく異なります。
1. self. で属性として保持する
python
コピーする
編集する
class Env:
def reset(self):
self.prev_dist = compute_initial_distance()
return initial_obs
def step(self, action):
# 前回の距離を self.prev_dist から読み出し…
delta = compute_distance() – self.prev_dist
# 更新して…
self.prev_dist = compute_distance()
return obs, reward, done, info
データの所在:self.prev_dist はインスタンス内に蓄えられ、reset()→step()→step()… とメソッドをまたいで共有できる。
用途:環境やオブジェクトが持つ「状態」を管理し、複数のメソッド呼び出しの間で読み書きするのに適している。
Gym での設計:reset() と step() の引数・戻り値の形式は固定なので、メソッド間での状態受け渡しは属性を使うのが一般的。
2. return を使って渡す
python
コピーする
編集する
def reset():
prev_dist = compute_initial_distance()
initial_obs = …
return initial_obs, prev_dist
# 呼び出し側で prev_dist を受け取り…
obs, prev_dist = reset()
def step(prev_dist, action):
dist = compute_distance()
delta = dist – prev_dist
reward = …
return obs, reward, dist # dist を次に返す
データの所在:prev_dist は呼び出し元のローカル変数になり、次の呼び出しで明示的に渡してあげないと失われる。
用途:単発の関数同士で一時的にデータをやり取りするのに向いている。関数型プログラミング的スタイル。
デメリット:
関数を呼ぶたびに前回の状態を引数として渡さねばならず、呼び出しコードが煩雑になる。
Gym のようにメソッドシグネチャが決まっている場合(step(self, action))、戻り値に勝手に状態を追加できない。
3. なにが変わるのか?
self. 属性 戻り値 (return)
保持期間 インスタンスのライフタイム全体 呼び出し元のローカルスコープまで
共有 クラスのどのメソッドからもアクセス可 呼び出し元から明示的に渡さないと共有不可
利便性 メソッド間で自動的に伝播 引数・戻り値のやり取りが必要
Gym 互換性 reset()/step() のシグネチャに依存せず状態管理可能 戻り値の数を変えられず使いづらい
結論
インスタンスの継続的な状態を保持したい場合は、やはり self. を使って属性として持たせるのが最も自然で安全です。
単発の関数間のデータ受け渡しや、クラスを使わない小規模スクリプトなら return で十分ですが、オブジェクト指向や標準的なインターフェース(Gym など)では属性による状態管理が必須となることが多いでしょう。
def close(self):
if p.isConnected():
p.disconnect()
コードの末尾ですが、closeの後に、ifなどがありますが、これでコードが終わっているのですか?
はい。この部分はクラス定義の最後のメソッド(close)で、その中にさらに if 文が入っているだけです。インデント(字下げ)で範囲を表しているので、ざっくり見るとこうなります:
python
コピーする
編集する
class EggCrackEnv(gym.Env):
… # 他のメソッド群
def close(self):
# ここのインデント(4 スペース)で close メソッドの本体開始
if p.isConnected():
# さらに 4 スペース分インデントが深くなって、
# if 文の中の処理(disconnect)を表す
p.disconnect()
# ここでインデントが戻る=close メソッドが終わり
# ここまででクラス定義が終わり
def close(self): の下に4スペース(あるいはタブ)字下げされたブロックが close メソッド の本体です。
その中の if 文は更に4スペース深く字下げされているので、「if の条件が真なら実行される処理」であることを示しています。
if の直後に字下げが戻る位置までが close の範囲で、そこまで書かれていればメソッドは完結です。
そのさらに外、クラスのインデントも戻っていれば、ファイル内ではこれでクラス定義およびコードが終了しています。
つまり
def close(self): ← メソッド定義開始
4スペース字下げの行 ← メソッド本体
さらに4スペース字下げの行 ← if 文の本体
インデントが元に戻ったらメソッド終了
その後インデントがクラス外まで戻ればクラスも終了
という構造になっているだけで、特に隠れた処理があるわけではなく、「ここでコードが終わり」です。