Pythonロボティクスコース レッスン 22
テーマ.6-1 2つのDCモーターを使用した車型ロボットの制作
車型ロボットを制作して前後左右に走行するプログラムを書こう
チャプター1
テーマ6を通して学ぶこと
テーマ6では全体を通して、オブジェクト指向のプログラム言語がもつ様々な特長を学び、それらを利用して効率的にプログラムを書く方法を詳しく見ていきます。はじめに、テーマ.6-1では車型ロボットを製作し、その制御を行うための「クラス」を定義します。続くテーマ.6-2~テーマ.6-4では、そのクラスを「クラスの継承」や「メソッドのオーバーライド」を利用して拡張し、「線に沿って走るライントレース機能」や「コントローラーで操作できる機能」を作成していきます。「継承」と「オーバーライド」の2つの新しい言葉が出てきましたが、順番に説明をしていきますので、まずはこのレッスンでクラスについて復習していきましょう。ここまでできたらクリック
チャプター2
このレッスンで学ぶこと
このレッスンでは、レッスン8(テーマ.2-3)で学習したプロパティとメソッドをもつオブジェクトの型となる「クラス」について振り返りを行います。そして、2つのDCモーターを使用して車型ロボットを製作し、その走行を制御するためのプロパティやメソッドをもつ独自のクラスを定義します。また、関数やメソッドの内容を変更せずに機能を拡張できる「デコレータ」という便利な仕組みについても合わせて学習します。
チャプター3
Pythonの文法の復習
ここでは、簡単なサンプルプログラムを通して、クラスについて復習しましょう。
3. 1 クラスについて
クラスは例えるなら、クッキーの型抜き器のようなものです。型抜き器が1枚1枚のクッキーの形を決めるのと同じように、クラスは1つ1つのオブジェクトの型を定義しています。同じクラスから作成されたオブジェクトは同じ名前のプロパティとメソッドを持っています。このようにクラスによってつくられたオブジェクトは「インスタンス」と呼ばれます。
3. 2 クラスの定義方法
クラスを定義するときは「class」というキーワードを使います。「 :(コロン)」の後にインデントを入れた部分がクラスの適用範囲になっていて、この中で定義された変数や関数は、「プロパティ」や「メソッド」と呼ばれます。
class MyClass: # クラス名の定義 property = "..." # プロパティの定義 def method(self, ...): # メソッドの定義 . .
例えば、次の「犬」の特徴をまとめたクラスを新たに定義する場合、次のようにコードを書きます。
【 サンプルコード 3-2-1 】
class Dog:
voice = 'Bow!' # 鳴き声のプロパティ
def bark(self): # プロパティで決められた鳴き声で吠える
print(self.voice)
プロパティは変数を定義するときと全く同じですが、メソッドは関数の定義と違うところがあります。それは、必ず1番目の引数として「self
」を渡すという点です。このself
はインスタンス自身を表しており、「.
(ドット)」を使ってプロパティやメソッドを呼び出すことができます。
※ 第1引数にself
以外の名前を付けても問題なくインスタンス自身が受け渡されます。しかし、特別な理由がない限りは分かりやすさからself
としておきましょう。
3. 3 クラスからインスタンスを作成する方法
インスタンスを作成するときは、次のように書きます。
インスタンス名 = クラス名()
実際に【 サンプルコード 3-2-1 】を実行し、Dog
クラスのインスタンスを作成して、プロパティやメソッドを呼び出してみましょう。また、メソッドの第1引数のself
は自動的に渡されるため、呼び出すときに指定する必要はありません。
>>> dog = Dog() >>> print(dog.voice) Bow! >>> dog.bark() Bow!
3. 4 コンストラクタという特別なメソッド
クラスからインスタンスを作成するときに自動的に実行されるメソッドを「コンストラクタ」と呼びます。この特別なメソッドは、「__init__
」という名前で定義します。
class MyClass: # クラス名の定義 def __init__(self, ...): # コンストラクタ . .
通常、コンストラクタの中では、インスタンスが独自に持つプロパティを設定したり、初期処理として必要なメソッドを実行したりします。
例として、【 サンプルコード 3-2-1 】に犬の名前をプロパティとして加えるコンストラクタを追加してみましょう。
【 サンプルコード 3-4-1 】
追加・変更【4行目、5行目、8行目】
class Dog:
voice = 'Bow!'
def __init__(self, name): # コンストラクタ
self.name = name # 犬の名前
def bark(self):
print(self.name, self.voice) # 名前も合わせて表示
では、【 サンプルコード 3-4-1 】を実行して、2匹の犬「Taro
」と「Jiro
」を作成し、それぞれbark()
メソッドを実行してみましょう。
>>> taro = Dog("Taro") >>> jiro = Dog("Jiro") >>> taro.bark() Taro Bow! >>> jiro.bark() Jiro Bow!
voice
がすべてのインスタンスが共通して同じ値をもつプロパティであるのに対し、name
はインスタンスごとに違う値をもつプロパティです。voice
のようなプロパティは「クラスメンバ変数」、name
のようなプロパティは「インスタンスメンバ変数」と呼ばれ、区別されています。ここまでできたらクリック
チャプター4
新しいPython文法の学習
ここでは、簡単なサンプルプログラムを通して、「デコレータ」について学習しましょう。
4. 1 デコレータについて
デコレータは、「引数として関数を受け取り、新たに別の関数を返す関数」です。ちょっとわかりづらいですが、ポイントはデコレータは関数であり、次の3つの性質を持っているということです。
- 関数を引数として受け取る
- 内部で新たな関数を定義する
- 2の関数を戻り値として返す
では、具体的なデコレータの例を見てみましょう。まずは、複数の数値の足し算と掛け算を行い、その結果を戻す2つの関数を用意します。
def addition(*args): # 足し算
result = 0
for num in args:
result += num
return result
def multiplication(*args): # 掛け算
result = 0
for num in args:
if(result == 0):
result = num # 1つめの数値のみそのまま代入
else:
result *= num
return result
これら2つの関数に新たにprint
文で結果を表示する機能を追加したいとします。そのために次のデコレータを定義します。
追加【1行目~5行目】
def decorator(func):
def new_func(*args): # 新たに定義した関数、funcと同じ引数を取る
result = func(*args) # funcに引数を渡して実行
print("Result:",result) # 追加で行う処理
return new_func # 新たに定義した関数を返す
def addition(*args):
result = 0
.
.
.
デコレータを追加する方法は2つあります。1つは次のように、デコレータ関数を実行し、その戻り値を変数に格納して再び実行する方法です。
>>> new_addition = decorator(addition) >>> new_addition(1, 2, 3) Result: 6
これだと少し手作業が増えてしまいます。もう1つの方法は、追加したい関数の直前に「@デコレータ関数名
」を書くことです。こちらの方がコードもすっきりとします。
【 サンプルコード 4-1-1 】
追加【7行目、14行目】
def decorator(func):
def new_func(*args):
result = func(*args)
print("Result:",result)
return new_func
@decorator
def addition(*args):
result = 0
for num in args:
result += num
return result
@decorator
def multiplication(*args):
result = 0
for num in args:
if(result == 0):
result = num
else:
result *= num
return result
この【 サンプルコード 4-1-1 】を実行して、それぞれの関数を呼び出してみましょう。
>>> addition(1, 2, 3) Result: 6 >>> multiplication(2, 3, 4) Result: 24
結果を表示する機能が見事に追加されています。このようにデコレータを利用することで、関数の内容を変更せずに機能を拡張することができるだけでなく、同じデコレータを別の関数へ再利用することができます。上の例では関数にデコレータを付与しましたが、メソッドにも付与することができます。以下の例は【 サンプルコード 3-4-1 】に、2回メソッドを実行するデコレータrepeat_twice
を付与したものです。
【 サンプルコード 4-1-2 】
追加【1行目~5行目、13行目】
def repeat_twice(func):
def new_func(self):
for _ in range(2):
func(self)
return new_func
class Dog:
voice = 'Bow!'
def __init__(self, name):
self.name = name
@repeat_twice
def bark(self):
print(self.name, self.voice)
(実行結果)
>>> dog = Dog("Taro") >>> dog.bark() Taro Bow! Taro Bow!
また、デコレータは重ねて付与することもできます。以下は上の例にさらに3回メソッドを実行するデコレータrepeat_three_times
を追加して付与しています。
【 サンプルコード 4-1-3 】
追加【7行目~11行目、19行目】
def repeat_twice(func):
def new_func(self):
for _ in range(2):
func(self)
return new_func
def repeat_three_times(func):
def new_func(self):
for _ in range(3):
func(self)
return new_func
class Dog:
voice = 'Bow!'
def __init__(self, name):
self.name = name
@repeat_three_times
@repeat_twice
def bark(self):
print(self.name, self.voice)
(実行結果)
>>> dog = Dog("Taro") >>> dog.bark() Taro Bow! Taro Bow! Taro Bow! Taro Bow! Taro Bow! Taro Bow!
チャプター5
車型ロボットの組み立て
それでは、ここからは車型ロボットの製作に入ります。早速、組立説明書を開き、手順に沿って車型ロボットの組み立てを行いましょう。
5. 1 組み立てに必要なパーツ
【 パーツ一覧 】
- Studuino:bit×1
- ロボット拡張ユニット×1
- 電池ボックス×1
- DCモーター×2
- ブロック基本四角(黒)×2
- ブロック基本四角(赤)×2
- ブロック三角(赤)×2
- ブロックハーフB(グレー)×1
- ブロックハーフB(黒)×2
- ブロックハーフB(赤)×2
- ブロックハーフC(白)×4
- ブロックハーフD(白)×6
- ステー×4
- 丸(目玉)×1
【 アーテックブロックの形状 】
5. 2 組立説明書
以下のリンク先から組立説明書を開いてください。
チャプター6
車型ロボットのプログラム作成
ここからは、組み立てた車型ロボットの動作を制御するプログラムを作成していきます。
6. 1 復習:DCモーターを制御するためのメソッド
DCモーターの制御を行うDCMotor
クラスには以下のメソッドがありました。
【 DCMotor
クラスのメソッド一覧 】
メソッド名(引数) | 動作 |
---|---|
__init__(pin) | コンストラクタ(インスタンスを作成するときに最初に実行されるメソッド)。引数pin にはDCモーターを接続した先の端子名として、"M1" または"M2" を指定します。 |
power(power) | 引数power に出力の大きさを「0~255」の範囲で指定して、DCモーターの速さを制御します。 |
cw() | DCモーターを正転方向に回転します。 |
ccw() | DCモーターを逆転方向に回転します。 |
stop() | DCモーターを接続した出力端子を開放することで、ゆっくりと回転が止まります。 |
brake() | DCモーターを接続した出力端子を短絡させて、ブレーキをかけて回転を止めます。 |
また、stop()
メソッドとbrake()
メソッドはどちらもDCモーターの回転を止める命令ですが、内部の電気制御の違いから、命令が実行されてから停止するまでに掛かる時間が異なります。
下の動画のように、stop()
メソッドはそれまでの回転の勢いが残ったままゆっくりと停止し、brake()
メソッドはその場ですぐに停止します。
【 stop()
メソッドとbrake()
メソッドの違い 】
6. 2 DCモーターの回転方向と車型ロボットの進行方向の関係
組み立てた車型ロボットでは、DCモーターの回転方向とタイヤの回転方向に次の図ような関係があります。
【 DCモーターの回転方向とタイヤの回転方向 】
そのため、左右2つのDCモーターそれぞれ次の組み合わせで回転させることで、車型ロボットの進行方向を制御します。
【 DCモーターの回転方向と車型ロボットの動作の関係 】
動作 | 図 | M1のDCモーター (左側のタイヤ) | M2のDCモーター (右側のタイヤ) |
---|---|---|---|
前進 | 逆転 | 逆転 | |
後退 | 正転 | 正転 | |
左回転 | 正転 | 逆転 | |
右回転 | 逆転 | 正転 | |
左に曲がる | 停止 | 逆転 | |
右に曲がる | 逆転 | 停止 |
「左回転」や「右回転」はその場で回り、「左に曲がる」や「右に曲がる」は、停止したタイヤを中心として、円弧を描くように回ります。「右や左への回転」は「右や左へ曲がる」ときと比べて、回るときに描く円の半径が半分になるため、およそ半分の時間で向きを変えることができます。
【 向きを変えるためのかかる時間の違い 】
6. 3 車型ロボットのクラス定義
これらの動作をそれぞれ関数として定義して利用することもできますが、プロパティ(属性情報)やメソッドをもつクラスとして定義する方が管理しやすく、また後のレッスンで車型ロボットの機能を拡張するときにも再利用しやすくなります。そこで、オリジナルのクラス「VehicleRobot
」を新たに定義し、次のプロパティとメソッドを持たせましょう。
※ 英語で「車」のことを「vehicle」といいます。
【 VehicleRobot
クラスで定義するプロパティ 】
プロパティ名 | 役割 |
---|---|
dcm_l | 左のタイヤに取り付けたDCモーターを扱うために、 DCMotorクラスのインスタンスを格納する。 |
dcm_r | 右のタイヤに取り付けたDCモーターを扱うために、 DCMotorクラスのインスタンスを格納する。 |
【 VehicleRobot
クラスで定義するメソッド 】
メソッド名 | 振る舞い |
---|---|
__init__() | 初期化関数。2つのDCMotorクラスのインスタンスや、 初期のDCモーターの速さを設定する。 |
move_forward() | 前進する |
move_backward() | 後退する |
rotate_left() | 左方向へその場で回転する ※左右のタイヤは互いに反対向きに回転 |
rotate_right() | 右方向へその場で回転する ※左右のタイヤは互いに反対向きに回転 |
curve_left() | 左方向に曲がる ※左のタイヤは停止し、右のタイヤのみ回転 |
curve_right() | 右方向に曲がる ※左のタイヤのみ回転し、右のタイヤは停止 |
stop() | ブレーキをかけずにゆっくりと止まる |
set_speed_to_left() | 左のタイヤの回転の速さを1~10の10段階で設定する |
set_speed_to_right() | 右のタイヤの回転の速さを1~10の10段階で設定する |
※「rotate
」は、日本語で「回転する」を意味しています。
■ コンストラクタ(__init__()
メソッド)の定義
コンストラクタである__init__()
メソッドでは、DCMotor
クラスのインスタンスを作成して、プロパティに格納する処理を行います。DCMotor
クラスのインスタンスを作成するときは引数として端子名の文字列("M1"
または"M2"
)が必要なため、__init__()
メソッドでも同じように、左と右のタイヤを取り付けたDCモーターの接続先の端子名を引数として受け取るようにしましょう。
from pyatcrobo2.parts import DCMotor
class VehicleRobot: # pin_l が左側の端子、pin_r は右側の端子
def __init__(self, *, pin_l, pin_r):
self.dcm_l = DCMotor(pin_l)
self.dcm_r = DCMotor(pin_r)
4行目で、__init__(self, *, pin_l, pin_r)
と定義しているように「*,
」より後ろの引数はキーワード引数として強制されるようになります。そのため、このクラスのインスタンスを作成するときは次のようにコードを書きます。
robo = VehicleRobot(pin_l="M1", pin_r="M2")
続けて、作成したDCMotor
クラスのインスタンスのpower()
メソッドを呼び出して、初期のDCモーターの出力の大きさを設定します。ここでは、100としておきましょう。また、プロパティを呼び出すときは必ず先頭にself.
を付けることに注意してください。
追加【7行目、8行目】
from pyatcrobo2.parts import DCMotor
class VehicleRobot:
def __init__(self, *, pin_l, pin_r):
self.dcm_l = DCMotor(pin_l)
self.dcm_r = DCMotor(pin_r)
self.dcm_l.power(100)
self.dcm_r.power(100)
■ 前進・後退を行うメソッドの定義
次に、車型ロボットの前進と後退のメソッドを定義します。
動作 | メソッド名 | self.dcm_l (左側のタイヤ) | self.dcm_r (右側のタイヤ) |
---|---|---|---|
前進 | move_forward() | ccw() :逆転 | ccw() :逆転 |
後退 | move_backward() | cw() :正転 | cw() :正転 |
上の表を確認して、それぞれ次のように定義しましょう。
追加【10行目~16行目】
from pyatcrobo2.parts import DCMotor
class VehicleRobot:
def __init__(self, *, pin_l, pin_r):
self.dcm_l = DCMotor(pin_l)
self.dcm_r = DCMotor(pin_r)
self.dcm_l.power(100)
self.dcm_r.power(100)
def move_forward(self):
self.dcm_l.ccw()
self.dcm_r.ccw()
def move_backward(self):
self.dcm_l.cw()
self.dcm_r.cw()
■ 左回転・右回転を行うメソッドの定義
続けて、車型ロボットの左回転と右回転のメソッドを定義します。
動作 | メソッド名 | self.dcm_l (左側のタイヤ) | self.dcm_r (右側のタイヤ) |
---|---|---|---|
左回転 | rotate_left() | cw() :正転 | ccw() :逆転 |
右回転 | rotate_right() | ccw() :逆転 | cw() :正転 |
上の表を確認して、それぞれ次のように定義しましょう。
追加【18行目~24行目】
※ インデントの位置に注意してください。
def move_backward(self):
self.dcm_l.cw()
self.dcm_r.cw()
def rotate_left(self):
self.dcm_l.cw()
self.dcm_r.ccw()
def rotate_right(self):
self.dcm_l.ccw()
self.dcm_r.cw()
■ 左に曲がる・右に曲がるを行うメソッドの定義
今度は、車型ロボットが左に曲がるときと右に曲がるときのメソッドを定義します。
動作 | メソッド名 | self.dcm_l (左側のタイヤ) | self.dcm_r (右側のタイヤ) |
---|---|---|---|
左に曲がる | curve_left() | barake() :停止 | ccw() :逆転 |
右に曲がる | curve_right() | ccw() :逆転 | brake() :停止 |
上の表を確認して、それぞれ次のように定義しましょう。
追加【26行目~32行目】
def rotate_right(self):
self.dcm_l.ccw()
self.dcm_r.cw()
def curve_left(self):
self.dcm_l.brake()
self.dcm_r.ccw()
def curve_right(self):
self.dcm_l.ccw()
self.dcm_r.brake()
■ 停止を行うメソッドの定義
また、動作を停止する2つのメソッドも定義します。
動作 | メソッド名 | self.dcm_l (左側のタイヤ) | self.dcm_r (右側のタイヤ) |
---|---|---|---|
ブレーキをかけずにゆっくりと止まる | stop() | stop() | stop() |
ブレーキをかけてピタっと止まる | brake() | brake() | brake() |
上の表を確認して、それぞれ次のように定義しましょう。
追加【34行目~40行目】
def curve_right(self):
self.dcm_l.ccw()
self.dcm_r.stop()
def stop(self):
self.dcm_l.stop()
self.dcm_r.stop()
def brake(self):
self.dcm_l.brake()
self.dcm_r.brake()
■ タイヤの回転の速さを設定するメソッドの定義
最後に、タイヤの回転の速さを制御するためのメソッドを定義します。
動作 | メソッド名 | self.dcm_l (左側のタイヤ) | self.dcm_r (右側のタイヤ) |
---|---|---|---|
左のタイヤの回転の速さを1~10の10段階で設定する | set_speed_to_left(speed) | power(power) | なし |
右のタイヤの回転の速さを1~10の10段階で設定する | set_speed_to_right(speed) | なし | power(power) |
これらのメソッドでは、引数speed
に1~10を指定して10段階でタイヤの回転の速さを制御します。しかし、このメソッドの内部で実行するDCMotor
クラスのpower()
メソッドは引数power
に0~255の範囲で出力の大きさを指定します。そのため、メソッド内部で数値の大きさを次の式であらじめ変換しておきます。
power = speed * 25
この式によって、以下の図のように速さを出力の大きさへ置き換わります。
また、引数speed
に整数以外の値が入力されたり、範囲外の値が指定されるとエラーが発生しますので、あらかじめメソッドの内部で回避しておきましょう。
追加【42行目~53行目】
def brake(self):
self.dcm_l.brake()
self.dcm_r.brake()
def set_speed_to_left(self, speed):
speed = int(speed) # 整数型に変換
speed = 1 if speed < 1 else speed # 1より小さいときは1に設定
speed = 10 if speed > 10 else speed # 10より大きいときは10に設定
power = speed * 25
self.dcm_l.power(power)
def set_speed_to_right(self, speed):
speed = int(speed)
speed = 1 if speed < 1 else speed
speed = 10 if speed > 10 else speed
power = speed * 25
self.dcm_r.power(power)
■ クラス定義の確認
これで、クラスに必要なプロパティとメソッドが定義できました。書いたコードに誤りがないか、次のサンプルコードと見比べて確認しましょう。
【 サンプルコード 6-3-1 】
from pyatcrobo2.parts import DCMotor
class VehicleRobot:
def __init__(self, *, pin_l, pin_r):
self.dcm_l = DCMotor(pin_l)
self.dcm_r = DCMotor(pin_r)
self.dcm_l.power(100)
self.dcm_r.power(100)
def move_forward(self):
self.dcm_l.ccw()
self.dcm_r.ccw()
def move_backward(self):
self.dcm_l.cw()
self.dcm_r.cw()
def rotate_left(self):
self.dcm_l.cw()
self.dcm_r.ccw()
def rotate_right(self):
self.dcm_l.ccw()
self.dcm_r.cw()
def curve_left(self):
self.dcm_l.brake()
self.dcm_r.ccw()
def curve_right(self):
self.dcm_l.ccw()
self.dcm_r.brake()
def stop(self):
self.dcm_l.stop()
self.dcm_r.stop()
def brake(self):
self.dcm_l.brake()
self.dcm_r.brake()
def set_speed_to_left(self, speed):
speed = int(speed)
speed = 1 if speed < 1 else speed
speed = 10 if speed > 10 else speed
power = speed * 25
self.dcm_l.power(power)
def set_speed_to_right(self, speed):
speed = int(speed)
speed = 1 if speed < 1 else speed
speed = 10 if speed > 10 else speed
power = speed * 25
self.dcm_r.power(power)
6. 4 クラスを定義したファイルをモジュールにして利用する
このVehicleRobot
クラスは次からのレッスンでも利用していきます。そこで、Studuino:bit内に保存して、モジュールとして利用できるようにしてみましょう。
■ Studuino:bit内にファイルを保存する
以下の手順で作成したファイルをStuduino:bit内に保存しましょう。
vehicle(.py)
と付けてPC上にファイルを保存します。- メニューの「ファイル」をクリックし、1のファイルをStuduino:bit内に保存します。
- ファイルの転送が完了したことを確認して、もう一度メニューの「ファイル」をクリック、ウィンドウを閉じます。
- Studuino:bitのリセットボタンを押します。
■ モジュールからクラスをインポートして利用する
メニューの「新規」をクリックして、新しいファイルを作成します。このファイルから保存したvechicle(.py)
モジュールからVehicleRobot
クラスをインポートして、インスタンスを作成してみましょう。
from vehicle import VehicleRobot
robo = VehicleRobot(pin_l="M1", pin_r="M2")
次の順番でメソッドを1秒おきに呼び出すプログラムを作成して、実際に動作するか確認しましょう。
1.前進:move_forward()
2.後退:move_backward()
3.左回転:rotate_left()
4.右回転:rotate_right()
5.左に曲がる:curve_left()
6.右に曲がる:curve_right()
7.ブレーキをかけて止まる:brake()
【 サンプルコード 6-4-1 】
追加【5行目~17行目】
from vehicle import VehicleRobot
import time
robo = VehicleRobot(pin_l="M1",pin_r="M2")
robo.move_forward()
time.sleep_ms(1000)
robo.move_backward()
time.sleep_ms(1000)
robo.rotate_left()
time.sleep_ms(1000)
robo.rotate_right()
time.sleep_ms(1000)
robo.curve_left()
time.sleep_ms(1000)
robo.curve_right()
time.sleep_ms(1000)
robo.brake()
6. 5 車型ロボットを走らせる
プログラムが問題なく動作することが確認できたら、次は以下のように車型ロボットを走らせるにはどのようなコードを書けば良いか考えてみましょう。それぞれのサンプルコードは最後に書いています。
【 逆S字を描くように走らせる 】
【 正方形を描くように走らせる 】
【 サンプルコード 6-5-1 】 逆S字を描くように走らせる
※ DCモーターの回転の速さは電池の残量によってプログラム上では同じ数値設定でも実際の速さが異なります。下記のプログラムでS字を描かない場合は、time.sleep_ms()
メソッドの引数を調整してください。
from vehicle import VehicleRobot
import time
robo = VehicleRobot(pin_l="M1", pin_r="M2")
robo.curve_right()
time.sleep_ms(6000)
robo.curve_left()
time.sleep_ms(6000)
robo.brake()
【 サンプルコード 6-5-2 】 正方形を描くように走らせる
※ 正方形を描くときは、「右回転」⇒「前進」の動きを3回繰り返しています。そのため、以下のようにfor
文を使用して簡潔にプログラムをまとめることもできます。
from vehicle import VehicleRobot
import time
robo = VehicleRobot(pin_l="M1", pin_r="M2")
robo.move_forward()
time.sleep_ms(2000)
for _ in range(3):
robo.rotate_right()
time.sleep_ms(1000)
robo.move_forward()
time.sleep_ms(2000)robo.brake()
チャプター7
課題:デコレータを利用した走行時間の制御機能の追加
チャプター6のプログラムでは、それぞれの動作を行う時間を制御するために、その都度time
モジュールのsleep_ms()
メソッドを呼び出していました。もし、この時間制御を各メソッドの引数に時間を指定するだけで行うことができれば、プログラムをより簡潔に書けるようになります。
例えば、今は2秒前進して停止させるときに、以下のように3行で書いていたコードが、
robo.move_forward() time.sleep_ms(2000) robo.brake()
次のように1行にまとめることができます。
move_forward(2000)
時間の制御は、引数として時間を受け取り、time.sleep_ms()
メソッドと作成したクラスのbrake()
メソッドを実行するという処理になるため、すべてのメソッドで共通の処理として扱うことができます。そこで、この課題ではチャプター4で学習した「デコレータ」を利用して、次の6つのメソッドに、内部のコードを変更することなく、時間制御の機能を追加してみましょう。
move_forward()
メソッドmove_backward()
メソッドrotate_left()
メソッドrotate_right()
メソッドcurve_left()
メソッドcurve_right()
メソッド
7. 1 プログラムの作成例
デコレータとして、vehicle(.py)
で定義しているVehicleRobot
クラスに次のtime_control()
メソッドを追加します。このデコレータは引数func
として、指定されたメソッドを受け取り、内部でこのメソッドの実行と、追加で実行する処理を定義した新たなメソッドを戻します。
追加【2行目、11行目~17行目】
from pyatcrobo2.parts import DCMotor
import time # timeモジュールを新たにインポートしていることに注意!
class VehicleRobot:
def __init__(self, *, pin_l, pin_r):
self.dcm_l = DCMotor(pin_l)
self.dcm_r = DCMotor(pin_r)
self.dcm_l.power(100)
self.dcm_r.power(100)
def time_control(func):
def new_func(self, duration=-1):
func(self)
if duration > 0:
time.sleep_ms(int(duration))
self.brake()
return new_func
新たなメソッドは、def new_func(self, duration=-1)
の通り、-1
をデフォルト値としてもつ引数duration
が追加されています。このように-1
をデフォルト値としていることで、14行目~16行目の処理で、引数が省略された場合は、time.sleep_ms()
メソッドとbrake()
メソッドが実行されず、元のメソッドと同じ処理が行われるようになっています。
そして、このデコレータを各メソッドの上に修飾すると、プログラムの完成となります。
【 サンプルコード 7-1-1 】
from pyatcrobo2.parts import DCMotor
import time
class VehicleRobot:
def __init__(self, *, pin_l, pin_r):
self.dcm_l = DCMotor(pin_l)
self.dcm_r = DCMotor(pin_r)
self.dcm_l.power(100)
self.dcm_r.power(100)
def time_control(func):
def new_func(self, duration=-1):
func(self)
if duration > 0:
time.sleep_ms(int(duration))
self.brake()
return new_func
@time_control
def move_forward(self):
self.dcm_l.ccw()
self.dcm_r.ccw()
@time_control
def move_backward(self):
self.dcm_l.cw()
self.dcm_r.cw()
@time_control
def rotate_left(self):
self.dcm_l.cw()
self.dcm_r.ccw()
@time_control
def rotate_right(self):
self.dcm_l.ccw()
self.dcm_r.cw()
@time_control
def curve_left(self):
self.dcm_l.brake()
self.dcm_r.ccw()
@time_control
def curve_right(self):
self.dcm_l.ccw()
self.dcm_r.brake()
def stop(self):
self.dcm_l.stop()
self.dcm_r.stop()
def brake(self):
self.dcm_l.brake()
self.dcm_r.brake()
def set_speed_to_left(self, speed):
speed = int(speed)
speed = 1 if speed < 1 else speed
speed = 10 if speed > 10 else speed
power = speed * 25
self.dcm_l.power(power)
def set_speed_to_right(self, speed):
speed = int(speed)
speed = 1 if speed < 1 else speed
speed = 10 if speed > 10 else speed
power = speed * 25
self.dcm_r.power(power)
>>> robo = VehicleRobot(pin_l=”M1″, pin_r=”M2″)
>>> robo.move_forward(2000)
と入れてみましょう。
(【 サンプルコード 6-4-1 】を参照)
チャプター8
おわりに
8. 1 このレッスンのまとめ
このレッスンでは、クラスの定義方法について振り返りを行い、新たに「デコレータ」という仕組みを学びました。レッスンの後半では、車型ロボットを製作し、その走行の制御を行うための独自クラスを定義しました。
また、実際にそのクラスを使って作成したオブジェクトで逆S字や四角を描くように走行させるプログラムを作成しました。これらの走行プログラムは、独自クラスを定義しなくても書くことはできますが、その場合それらのプログラムを他の走行プログラムへ再利用することは難しくなります。
今回のようにクラスとして共通で使用できそうな処理をまとめておくことで、他のプログラムへも再利用しやすくなり、必要な機能を最小限の労力でつくれるようになります。
8. 2 次のレッスンについて
次のレッスンでは、オブジェクト指向なプログラム言語がもつ重要な特長として、「クラスの継承」と「メソッドのオーバーライド」について詳しく説明します。また、このれらの特長を活かして、今回製作した車型ロボットに、紙に描かれた線に沿って走行する「ライントレース機能」を追加します。