今回は、若干発展的ですが、プログラミングの上達に欠かせない考え方を導入します。
ちょっとややこしいかもしれませんが、そのうちできるようになればいいので気楽に聞いてください。
本編でプログラミングの基礎は終えました。世の中のコードの多くが、これまでの内容を組み合わせたものです。
しかし、単純にコードを書き連ねていくと、複雑なものを実装した際、1万行を超えてしまうなんてことも起こります。そうなると、どこに何を書いたのか把握するのはほぼ不可能になり、コード修正も非常に困難になります。つまり、人間の性能がボトルネックになってしまう訳です。
10行目から1000行目でこういう計算をして〜
2000行目でプロットして〜
と書き連ねていると訳わからなくなる!
プログラミングの歴史上、実際にこういう問題に直面しました。
そこで導入されたアイデアの一つが、関数(メソッド)を用いるというものです。
メソッドとは、いくつかの文をまとめてそれを一つの処理として名前をつけたもので、部品の最小単位になります。例えば、計算をするメソッド、グラフをかくメソッドのように分けるのです。このようにすれば、「計算はできているのにグラフがおかしい」場合には、グラフをかくメソッドを調べれば良いとすぐに分かります。イメージは以下のような感じです。
計算をするメソッド:
具体的な計算処理
グラフをかくメソッド:
プロットの処理内容
計算メソッドを実行
グラフメソッドを実行
(名前をつけて部品化!)
例えばこんな感じで部品化します。
def print_hello(): # 関数を定義
print("Hello world")
print_hello() # 関数を実行
Hello world
def print_hello()
でprint_helloという名前の部品を作成しました。名前をつけたことで、この部分が何をしているのか人間が把握しやすくなります。
さらに、メソッドは繰り返し使用することができるので、コードがシンプルになるのも利点です。
def print_hello():
print("Hello world")
print_hello()
print_hello()
このようにメソッドを繰り返し呼び出すことができます。
現段階ではメソッドの中身もコード全体もシンプルなので、ありがたみが実感しにくいと思いますが、だんだんわかってくると思います。
引数(ひきすう)
関数(メソッド)を呼び出す際に、値を渡すことができます。この渡される値のことを引数と呼びます。数学の関数と引数の関係をイメージしてもらえれば良いでしょう。例えばこんな感じです。
def print_method(s):
print(s)
s = "Hello world"
print_method(s)
print_method関数の引数がsになっています。今回は、sをprintしてねというメソッドを作成しました。このprint_methodを呼び出す際に、sとして"Hello world"を代入してあります。だからこのコードの実行結果は
Hello world
となります。
ちょうどf(x)=sin(x)という関数に対して、$x=\pi/6$を代入すると、1/2が返ってくるような感じですね。
当然代入する値を変えれば、違う結果が返ってきます。
def print_method(s):
print(s)
s = "Hello world"
print_method(s)
s = "Let's phyisics!"
print_method(s)
Hello world
Let's physics!
また、引数として複数の値を渡すこともできます。
def calculate(x, y):
z = x*y
print(z)
x = 20
y = 24
calculate(x, y)
480
x=20という値と、y=24という値がcalculateメソッドに渡されて、掛け算されたものがprintされました。
ちなみに、メソッドは呼び出されて初めて実行されます。コードの初めで定義しても、呼び出されない限り実行されることはありません。今回だと、コードの一番下で呼び出されて初めて実行されています。
戻り値
先ほどメインのコードからメソッドへ引数として値を渡しましたが、逆にメソッドからメインへ値を渡すこともできます。
def calculate(x, y):
z = x*y
return z
x = 20
y = 24
z = calculate(x, y)
print(z)
480
一見何も変わっていませんが、returnを用いたことで、calculateメソッドで得られた値がメインに渡されています。今回はメインに渡してからメインでprintしていますね。
値を渡しているので、メイン内でさらに計算することもできます。
def calculate(x, y):
z = x*y
return z
x = 20
y = 24
z = calculate(x, y)
z += 2
print(z)
482
例題
第4回のコード(以下)を「計算をするメソッド」「プロットをするメソッド」に分けることで、コードをわかりやすくせよ。
import numpy as np
import matplotlib.pyplot as plt
t = 0 # [s]
h = 333 # [m]
v0 = 50 # [m/s]
theta = 45/180*np.pi # [rad]
xData = []
yData = []
x = 0
y = h
while y>0:
x = v0*np.cos(theta)*t
y = h + v0*np.sin(theta)*t - 0.5*9.8*t**2
xData.append(x)
yData.append(y)
t = t+0.1
# 軸の設定
plt.xlim(0, v0 * np.cos(theta) * t * 1.1)
plt.ylim(0, h*2)
plt.xlabel('x [m]')
plt.ylabel('y [m]')
plt.plot(xData, yData)
plt.show()
解答例
何をしているのかが分かりやすい名前をつけましょう。
引数を何にするのかが難しいと思いますが、全体で使うものは関数の外で宣言して、パラメタtのように関数内でしか使わないものは関数内で宣言しましょう。
少しずつ慣れていけばOKです。
コメントがなくても、この部分が何をしているのかが分かりやすいコードになりました。
import numpy as np
import matplotlib.pyplot as plt
h = 333 # [m]
v0 = 50 # [m/s]
theta = 45/180*np.pi # [rad]
xData = []
yData = []
def solve_EoM(h, v0, theta):
t = 0 # [s]
dt = 0.1
x = 0
y = h
while y>0:
x = v0*np.cos(theta)*t
y = h + v0*np.sin(theta)*t - 0.5*9.8*t**2
xData.append(x)
yData.append(y)
t = t+dt
return xData, yData
# 軸の設定
def figure_setting(xData, yData):
plt.xlim(0, max(xData) * 1.1)
plt.ylim(0, max(yData) * 1.1)
plt.xlabel('x [m]')
plt.ylabel('y [m]')
xData, yData = solve_EoM(h, v0, theta)
figure_setting(xData, yData)
plt.plot(xData, yData)
plt.show()
なんとなくやりたいことが掴めてきたでしょうか。
とにかく、長くて複雑なコードを部品に分けることで、人間が把握しやすくしてあげようというのがモチベーションです。
おまけ
このように、メソッドを用いて部品に分けることでわかりやすくする方法があるのですが、コードが長くなるとメソッドがたくさん登場してしまい、これでもだんだん複雑になっていきます。この問題を解消するために、メソッドを種類ごとに分ける方法があるのですが、今回は長くなるので扱いません。
いかがだったでしょうか。
少しややこしい上に、現段階ではありがたみが実感しにくかったかもしれません。それでも実際にコードを組む上では非常に大事な考え方なので、少しずつ身につけてください。
分かりやすい参考書もたくさんあるので、ぜひ読んでみてください。
- 第0回 はじめに
- 第1回 プログラミングをしてみよう
- 第2回 if文、for文を使ってみよう
- 第3回 グラフを描いてみよう
- 第4回 物理の計算をしてみよう
- 応用編 関数(メソッド)を使ってみよう
- 番外編 環境構築をしてみよう