1-2. 変数と関数の基礎

変数と関数の基礎について説明します。

参考

変数

プログラミング言語における変数とは、値に名前を付ける仕組みであり、名前はその値を指し示すことになります。

[1]:
h = 188.0

以上のように = を用いる構文によって、188.0 という値に h という名前が付きます。これを変数定義と呼びます。

定義された変数は、式の中で使うことができます。h という変数自体も式なので、h という式を評価することができ、変数が指し示す値が返ります。

[2]:
h
[2]:
188.0

異なる変数は、いくらでも導入できます。たとえば、以下では w を変数定義します。

[3]:
w = 104.0

ここで、h を身長 (cm)、w を体重 (kg) の意味と考えると、次の式によってBMI(ボディマス指数)を計算できます。

[4]:
w / (h/100.0) ** 2
[4]:
29.425079221367138

なお、演算子 ** の方が / よりも先に評価されることに注意してください。

変数という名前の通り、変数が指し示す値を変えることもできます。

[5]:
w = 104.0-10

このように変数を再定義すれば、元々 w が指し示していた値 104.0 を忘れて、新たな値 94.0 を指し示すようになります。 この後で、前と同じBMIの式を評価してみると、w の値の変化に応じて、BMIの計算結果は変わります。

[6]:
w / (h/100.0) ** 2
[6]:
26.595744680851066

なお、未定義の変数(たとえば BMI)を式の中で用いると、次のようにエラーが生じます。

---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-1-b910749d4383> in <module>
----> 1 BMI  # 未定義の変数

NameError: name 'BMI' is not defined

次のセルの行頭にある # を削除して実行してみましょう。

[7]:
# BMI  # 未定義の変数

以降では、単純のため、変数が指し示す値を、変数の値として説明していきます。

代入文

変数定義に用いた = による構文を、Pythonでは代入文 (assignment statement) と呼びます。 そして、代入文を実行することを代入 (assignment) と言います。 代入文は、= の左辺に右辺の式の評価結果の値を割り当てる文です。 上記の例のように、左辺が変数の場合には、代入文は変数定義と解釈されます。

代入文は、右辺を評価した後に左辺に割り当てるという順番に従います。右辺に出現する変数が左辺に出て来てもかまいません。

[8]:
w = w-10

上の代入文は、w の値を 10 減らす操作となります。= は数学的な等号ではないことに注意してください。

もう一度BMIを計算してみると、w の値が減ったことで、先と結果が変わります。

[9]:
w / (h/100.0) ** 2
[9]:
23.766410140334994

注意: 数学における代入は、substitution(置換)であり、プログラミング言語における代入 (assignment) とは異なります。代入という単語よりも、assignment(割り当て)という単語で概念を覚えましょう。

累積代入文

上の例のように変数の値を減らす操作は、次のような累算代入文 (augmented assignment statement) を使って簡潔に記述することができます。

[10]:
w -= 10

ここで、-= という演算子は、-= を結合させた演算子で、w = w - 10 という代入文と同じ意味になります。 これは代入文と2項演算が複合したものであり、- に限らず、他の2項演算についても同様に複合した累算代入文が利用できます。たとえば、変数の値を増やすには += という演算子を用いることができます。

[11]:
w += 10

= も含めて、これらの演算子は代入演算子と呼ばれています。代入演算子によって変数の値がどのように変わるか、確かめてください。

[ ]:

関数の定義と返値

前述のように、変数の値が変わるたびにBMIの式を入力するのは面倒です。以下では、身長 height と体重 weight をもらって、BMIを計算する関数 bmi を定義してみましょう。関数を定義すると、BMIの式の再入力を省けて便利です。

次のような形式で、関数定義を記述できます。

関数定義など、複数行のコードセルには、行番号を振るのがよいかもしれません。行番号を振るかどうかは、コマンドモードでエルの文字(大文字でも小文字でもよいです)を入力することによって、スイッチできます。行番号があるかないかは、コードの実行には影響しません。

[12]:
def bmi(height, weight):
    return weight / (height/100.0) ** 2

Python では、関数定義は、上のような形をしています。 最初の行は以下のように def で始まります。

def 関数名(引数, ...):

引数(ひきすう)とは、関数が受け取る値を指し示す変数のことです。仮引数(かりひきすう)ともいいます。

: 以降は関数定義の本体であり、関数の処理を記述する部分として以下の構文が続きます。

return 式

この構文は return で始まり、return文と呼ばれます。return文は、return に続く式の評価結果を、関数の呼び出し元に返して(これを返値と言います)、関数を終了するという意味を持ちます。この関数を、入力となる引数とともに呼び出すと、return の後の式の評価結果を返値として返します。

ここで、Pythonでは、 return の前に空白が入ることに注意してください。 このような行頭の空白をインデントと呼びます。 Pythonでは、インデントの量によって、 構文の入れ子を制御するようになっています。このことについては、 より複雑な構文が出てきたときに説明しましょう。

上記では、def の後に続く bmi が関数名です。それに続く括弧の中に書かれた heightweight は、引数です。また、return の後にBMIの計算式を記述しているので、関数の呼び出し元にはBMIの計算結果が返値として返ります。

では、定義した関数 bmi を呼び出してみましょう。

[13]:
bmi(188.0,104.0)
[13]:
29.425079221367138

第1引数を身長(cm)、第2引数を体重(kg)としたときのBMIが計算されていることがわかります。

関数呼び出しは演算式の一種なので、引数の位置には任意の式を記述できますし、 関数呼び出し自体も式の中に記述できます。

[14]:
1.1*bmi(174.0, 119.0 * 0.454)
[14]:
19.628947020742505

もう1つ関数を定義してみましょう。

[15]:
def felt_air_temperature(temperature, humidity):
    return temperature - 1 / 2.3 * (temperature - 10) * (0.8 - humidity / 100)

この関数は、温度と湿度を入力として、体感温度を返します。 このように、関数名や変数名には _ (アンダースコア)を含めることができます。 アンダースコアで始めることもできます。

数字も関数名や変数名に含めることができますが、 名前の最初に来てはいけません。

[16]:
felt_air_temperature(28, 50)
[16]:
25.652173913043477

なお、return の後に式を書かないと、何も返されなかったことを表現するために、「何もない」ことを表す None という特別な値が返ります。 (None という値は色々なところで現れることでしょう。)

return文に到達せずに関数定義本体の最後まで行ってしまったときも、None という値が返ります。

予約語

Pythonでの defreturn は、関数定義やreturn文の始まりを記述するための特別な記号であり、それ以外の用途に用いることができません。 このように構文上で役割が予約されている語は、予約語と呼ばれます。 Codeセルの構文ハイライトで(太字緑色などで)強調されるものが予約語だと覚えておけば大体問題ありません。

練習 ft_to_cm

f フィート i インチをセンチメートルに変換する関数 ft_to_cm(f,i) を定義してください。 ただし、1 フィート = 12 インチ = 30.48 cm としてよい。

[17]:
def ft_to_cm(f, i):
    ...

定義ができたら、次のセルを実行して、エラーがでないことを確認してください。

[18]:
assert round(ft_to_cm(5, 2) - 157.48, 6) == 0
assert round(ft_to_cm(6, 5) - 195.58, 6) == 0
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
/tmp/ipykernel_767/651702340.py in <module>
----> 1 assert round(ft_to_cm(5, 2) - 157.48, 6) == 0
      2 assert round(ft_to_cm(6, 5) - 195.58, 6) == 0

TypeError: unsupported operand type(s) for -: 'NoneType' and 'float'

練習 quadratic

二次関数 \(f(x) = ax^2+bx+c\) の値を求める quadratic(a,b,c,x) を定義してください。

[19]:
def quadratic(a, b, c, x):
    ...

定義ができたら、次のセルを実行して、エラーがでないことを確認してください。

[20]:
assert quadratic(1, 2, 1, 3) == 16
assert quadratic(1, -5, -2, 7) == 12
---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
/tmp/ipykernel_767/2102554972.py in <module>
----> 1 assert quadratic(1, 2, 1, 3) == 16
      2 assert quadratic(1, -5, -2, 7) == 12

AssertionError:

ローカル変数

次の関数は、ヘロンの公式によって、 与えられた三辺の長さに対して三角形の面積を返すものです。

[21]:
import math

def heron(a,b,c):
    s = 0.5*(a+b+c)
    return math.sqrt(s * (s-a) * (s-b) * (s-c))

math.sqrt を使うために import math を行っています。

次の式を評価してみましょう。

[22]:
heron(3,4,5)
[22]:
6.0

この関数の中では、まず、3辺の長さを足して 2 で割った(0.5 を掛けた)値を求めています。 そして、その値を s という変数に代入しています。 この s という変数は、この関数の中で代入されているので、この関数の中だけで利用可能な変数となります。 そのような変数をローカル変数と呼びます。

そして、s を使った式が計算されてreturn文で返されます。 ここで、関数定義のひとまとまりの本体であることを表すために、s への代入文もreturn文も、同じ深さでインデントされていることに注意してください。

Pythonでは、関数の中で定義された変数は、その関数のローカル変数となります。関数の引数もローカル変数です。関数の外で同じ名前の変数を使っても、それは関数のローカル変数とは「別もの」と考えられます。

heron を呼び出した後で、関数の外で s の値を参照しても、以下のように、s が未定義という扱いになります。

[23]:
s
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
/tmp/ipykernel_767/1028141915.py in <module>
----> 1 s

NameError: name 's' is not defined

以下では、heron の中では、s というローカル変数の値は 3 になりますが、関数の外では、s という変数は別もので、その値はずっと 100 です。

[24]:
s = 100
heron(3,4,5)
[24]:
6.0
[25]:
s
[25]:
100

print

上の例で、ローカル変数は関数の返値を計算するのに使われますが、それが定義されている関数の外からは参照することができません。

ローカル変数の値など、関数の実行途中の状況を確認するには、 print というPythonが最初から用意してくれている関数(組み込み関数)を用いることができます。この print を関数内から呼び出すことでローカル変数の値を確認できます。

print は任意個の引数を取ることができ、コンマ , の区切りには空白文字が出力されます。引数を与えずに呼び出した場合には、改行のみを出力します。

[26]:
def heron(a,b,c):
    s = 0.5*(a+b+c)
    print('The value of s is', s)
    return math.sqrt(s * (s-a) * (s-b) * (s-c))
[27]:
heron(1,1,1)
The value of s is 1.5
[27]:
0.4330127018922193

このように print 関数を用いて変数の値を観察することは、プログラムの誤り(バグ)を見つけ、修正(デバッグ)する最も基本的な方法です。これは1-4でも改めて説明します。

なお、以降の説明では、 print 関数を呼び出して値を出力することを「印字する」と表現します。

コメントと空行

コメントについては既に説明しましたが、 関数定義にはコメントを付加して、後から読んでもわかるようにしましょう。

コメントだけの行は空行(空白のみから成る行)と同じに扱われます。

関数定義の中に空行を自由に入れることができますので、 長い関数定義には、区切りとなるところに空行を入れるのがよいでしょう。

[28]:
# heronの公式により三角形の面積を返す
def heron(a,b,c): # a,b,c は三辺の長さ

    # 辺の合計の半分をsに置く
    s = 0.5*(a+b+c)
    print('The value of s is', s)

    return math.sqrt(s * (s-a) * (s-b) * (s-c))

関数の参照の書き方

関数は、

  関数 heron は、三角形の三辺の長さをもらって三角形の面積を返します。

というように、名前だけで参照することもありますが、

  heron(a,b,c) は、三角形の三辺の長さ a, b, c をもらって三角形の面積を返します。

というように、引数を明示して参照することもあります。

ときには、

  heron() は三角形の面積を返します。

のように、関数名に () を付けて参照することがあります。 この記法は、heron が関数であることを明示しています。

関数には引数がゼロ個のものがあるのですが、heron() と参照するとき、 heron は必ずしも引数の数がゼロ個ではないことに注意してください。

後に学習するメソッドという関数の親戚に対しても同様の記法が用いられます。

練習 qe_disc qe_solution1 qe_solution1

二次方程式 \(ax^2 + bx + c = 0\) に関して以下のような関数を定義してください。

  1. 判別式 \(b^2 - 4ac\) を求める qe_disc(a,b,c)

  2. 解のうち、大きくない方を求める qe_solution1(a,b,c)

  3. 解のうち、小さくない方を求める qe_solution2(a,b,c)

ただし、qe_solution1qe_solution2qe_disc を使って定義してください。 二次方程式が実数解を持つと仮定してよいです。

[29]:
import math

def qe_disc(a, b, c):
    ...

def qe_solution1(a, b, c):
    ...

def qe_solution2(a, b, c):
    ...

定義ができたら、次のセルを実行して、エラーがでないことを確認してください。

[30]:
assert qe_disc(1, -2, 1) == 0
assert qe_disc(1, -5, 6) == 1
assert round(qe_solution1(1, -2, 1) - 1, 6) == 0
assert round(qe_solution2(1, -2, 1) - 1, 6) == 0
assert round(qe_solution1(1, -5, 6) - 2, 6) == 0
assert round(qe_solution2(1, -5, 6) - 3, 6) == 0
---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
/tmp/ipykernel_767/2182455849.py in <module>
----> 1 assert qe_disc(1, -2, 1) == 0
      2 assert qe_disc(1, -5, 6) == 1
      3 assert round(qe_solution1(1, -2, 1) - 1, 6) == 0
      4 assert round(qe_solution2(1, -2, 1) - 1, 6) == 0
      5 assert round(qe_solution1(1, -5, 6) - 2, 6) == 0

AssertionError:

▲グローバル変数

Pythonでは、関数の中で代入が行われない変数は、グローバル変数とみなされます。

グローバル変数とは、関数の外(トップレベルもしくはモジュールレベルと呼ばれます)で定義される変数のことです。

グローバル変数は、関数の中から参照することができます。

[31]:
g = 9.8
[32]:
def force(m):
    return m*g

以上のように force を定義すると、 force の中で g というグローバル変数を参照することができます。

[33]:
force(104)
[33]:
1019.2
[34]:
g = g/6

以上のように、g の値を変更してから force を実行すると、 変更後の値が用いられます。

[35]:
force(104)
[35]:
169.86666666666667

以下はより簡単な例です。

[36]:
a = 10
def foo():
    return a
def bar():
    a = 3
    return a
[37]:
foo()
[37]:
10
[38]:
bar()
[38]:
3
[39]:
a
[39]:
10
[40]:
a = 20
[41]:
foo()
[41]:
20

bar の中では a への代入があるので、a はローカル変数になります。 ローカル変数の a とグローバル変数の a は別ものと考えてください。 ローカル変数 a への代入があっても、グローバル変数の a の値は変化しません。 foo の中の a はグローバル変数です。

[42]:
def boo(a):
    return a
[43]:
boo(5)
[43]:
5
[44]:
a
[44]:
20

関数の引数もローカル変数の一種と考えられ、グローバル変数とは別ものです。

練習の解答

[45]:
def ft_to_cm(f, i):
    return 30.48*f + (30.48/12)*i
[46]:
def quadratic(a, b, c, x):
    return a*x*x + b*x + c
[47]:
import math

def qe_disc(a, b, c):
    return b*b - 4*a*c

def qe_solution1(a, b, c):
    return (-b - math.sqrt(qe_disc(a, b, c))) / (2*a)

def qe_solution2(a, b, c):
    return (-b + math.sqrt(qe_disc(a, b, c))) / (2*a)