小休止
小休止
今回は難易度を下げて、今まで扱ってこなかったちょっといいネタをここで紹介します。
無くても困らなかったけど、知ってしまったらもう無いと困るというくらい、それはそれはもう依存度の高いものです。
ここまで理解半分だったとしても、なんとか読めてこれた方なら超簡単なので、ここを読んで今のうちに頭を休めておいてください。
「むむ、此奴・・・できる!!」と思わせられるようになります。👍
今回は難易度を下げて、今まで扱ってこなかったちょっといいネタをここで紹介します。
無くても困らなかったけど、知ってしまったらもう無いと困るというくらい、それはそれはもう依存度の高いものです。
ここまで理解半分だったとしても、なんとか読めてこれた方なら超簡単なので、ここを読んで今のうちに頭を休めておいてください。
スコープとは、「値の有効範囲」をいいます。
スコープという言葉自体はプログラミングには必須ではないのですが、プログラムを組む上では必須といえる概念です。
これまでもスコープを考慮しながら正しく動くようにサンプルプログラムを紹介してきました。
例えば関数に引数を渡すのがそうです。
def add(a, b):
c = a + b
return c
a, b = 1, 2
print(add(a, b))
3
これをメインプログラムと関数に分けてみます。
def add(a, b): # 関数
c = a + b
return c
a, b = 1, 2 # メインプログラム
print(add(a, b))
メインプログラムで作成した a
と b
の変数に代入されたそれぞれ 1
と 2
をadd()の引数として関数内で使えるように渡しています。
これを試しに引数を渡さずにプログラムを実行するとどうなるか、見てみましょう。
def add(): # ← 引数を受け取らない
c = a + b # ← メインプログラム中の a, b を使おうと考える
return c
a, b = 1, 2
print(add()) # ← 引数を渡さない
エラーが出ると思いました?🤭 カシコイ!
メインプログラムが書かれている、この地べたに書かれたプログラム部分は『グローバルスコープ (global scope)』といって、このスコープ内で作られた値は、プログラムの上から下までの全域において有効となるのです。
一方、関数の領域は『ローカルスコープ (local scope)』といって、この中で作られた値は別のスコープでは使えません。
def add(): # ← 引数を受け取らない
c = a + b # ← main()中の a, b を使おうと考える
return c
def main():
a, b = 1, 2
print(add()) # ← 引数を渡さない
main()
関数の main() と add() はそれぞれ異なるローカルスコープなので、この場合は引数をきちんと渡さないと正しく動かないのです。
def add(a, b): # ← 引数を受け取る
c = a + b
return c
def main():
a, b = 1, 2
print(add(a, b)) # ← 引数を渡す
main()
グローバルスコープで作られた変数を『グローバル変数』
ローカルスコープで作られた変数を『ローカル変数』といいます。
逆にローカル変数をグローバルスコープで使おうとするとどうなるでしょうか?
def add(a, b):
c = a + b
return # ← 戻り値を None で返す
a, b = 1, 2
add(a, b) # ← ここは None になるだけ
print(c) # ← 関数内で作られた変数 c の値を出力する
ローカル変数はそのローカルスコープ内でしか使えないので、エラーになってしまいます。
###### グローバルスコープ ######
# ***** ローカルスコープ ***** #
# * def add(): * #
# * c = a + b * #
# * return c * #
# ************************** #
# #
# a, b = 1, 2 #
# c = add() #
# print(c) #
##############################
関数型でプログラムを組むと、引数だらけになってごちごちゃしてわけわからなくなるのがイヤで、私は関数型のプログラムは倦厭しています。こういう場合はクラス型のほうが断然(・∀・)イイ!!
ちなみにオブジェクト指向のクラスにもスコープの概念があります。ですので関数からメソッドにアクセスする際は、メソッドに引数を渡す必要があります。
わかるようになるまでは混乱するかもしれませんが、わかるようになったら呼吸をするように自然と組めるようになります。断言!
1章の繰り返し文 chatpter.4 - break を紹介したときに、continue というのもあるよとチラッと紹介していました。
break は、break の時点で今のループを1つだけ強制的に抜けるものでした。
continue は、continue 以降のプログラムを実行せずに、に戻って次のループに移ります。
for i in range(10):
if i % 2 == 0:
continue
print(i)
1
3
5
7
9
i
が偶数の時は、continue の時点でに戻り、次のループを実行します。
結果、奇数のみが出力されることとなります。
ただ、 〜 else文でも同じことができますし、
for i in range(10):
if i % 2 == 1:
print(i)
という様にしても同じ結果になりますし、むしろこのほうが完結でわかりやすくなってしまったりします。
ではどんな時に使ったらよいか。
それは、本当にここで使うべきというときに使います。ってこれじゃわかりませんね。😓
まずは「これ以降のプログラムを実行しないで、ここで continue します。」と明示的に使います。
上の continue を使わないプログラムのほうで、もし条件文の中身が長くなってしまうと条件文の管轄が大きすぎて扱いにくくなってしまいます。
その場合、「条件式が True の時に、以下を実行する」のではなくて、「条件式が True の時に、以降を実行しないでここで continue する」とすれば、再帰関数の終了条件みたいに、continue の条件を書くだけで過剰なインデントでガタガタとならずに読みやすくなる・・・かもしれません。
もちろんただ continue するだけでなく、なにかをしてから continue することも可能です。
for i in range(10):
if i % 2 == 0:
print('even')
continue
print(i)
even
1
even
3
even
5
even
7
even
9
関数では return となった所で return するように、こちらも continue となった所で continue となります。 break も同様です。
pass は、言葉通り何もしないでパスします。
通常ならこの様にこれだけ書くとエラーになりますが、
def main():
File "Main.py", line 2
^
SyntaxError: unexpected EOF while parsing
関数の中身にpass だけを書くとエラーが起こりません。
def main():
pass
エラーだけでなく、他に何も起こりませんが。😅
以下のプログラムの様な書き方をしてみます。
class Calc:
def __init__(self):
pass
def calc(self):
pass
def addition(self):
pass
def subtraction(self):
pass
def multiplication(self):
pass
def division(self):
pass
def main():
pass
if __name__ == '__main__':
main()
まずおおまかな構成を決めてから各関数やメソッドに合った機能のプログラムを書いていくと、全体像を把握しやすくなり、一から順に書いていくよりもプログラムが組みやすくなるかと思います。
pass は、本当に何もしないだけのものなので、
print(0)
pass
print(1)
0
1
という感じになります。不要になったら pass はすぐに除去しましょう。
文や文でも使えます
# じゃんけんを10回するところ
for i in range(10):
pass
とか、
for i in range(10):
if i % 2 == 0:
pass # 偶数の時の処理
else:
pass # 奇数の時の処理
とかしておくと、全体を見ながらあとでじっくりプログラムを組んでいけるかなと思います。
ただし、文で使うときには注意してください!
while n < 10:
pass
n
が10未満だった場合、値が変化しないので無限ループします。🙀
文で使うときには必ず、条件式を False にしておきましょう。
while n < 10 and False:
pass
pass とハサミは使いよう。工夫して使ってみてください。😽
else は文にも使えます。
for i in range(0, 10, 2):
if i % 2 == 1:
print(i, 'は奇数です')
break
else:
print('すべて偶数です')
すべて偶数です
文が最後まで正常に実行された時にのみ、
else 以降が実行されます。
反対に、break などでループを途中で抜けたときには無視されます。
for i in range(0, 10, 3): # step を 3 にする
if i % 2 == 1:
print(i, 'は奇数です')
break
else:
print('すべて偶数です')
3 は奇数です
この 〜 else文は学習段階ではあまり使われないのですが、私はむしろ積極的に使っていって良い構文だと思っています。
上のプログラムを、else を使わないで書くと、
odd = False
for i in range(0, 10, 2):
if i % 2 == 1:
odd = True
num = i
break
if odd:
print(num, 'は奇数です')
else:
print('すべて偶数です')
すべて偶数です
いろいろな書き方ができますが、だいたいこんな書き方になります。
〜 else文のほうは 文1つの中にすべてプログラムが収まりましたが、後者のほうはプログラムがバラけてしまいました。
どちらがわかりやすいかは慣れもあり、人それぞれなのですが、私は 〜 else文のほうが断然わかりやすいと思うのですが、いかがでしょうか?
まずは次の表を見てください。
代入文 | = | 値を変数に代入する。 |
代入式 | := | 値を変数に代入する。 |
説明に困るくらい同じことをしているのですが😓、性質は異なります。
まだなんじゃらほい(死語)ですが、使う所も異なり、しっかり棲み分けもされています。
※ 初期化された変数に再び代入することを「再代入」といいます。
a += 1 や a = a + 1、また a = 1 とした後に a = 3 とすることも再代入といいます。これらも代入文です。
ただの理屈なので、これは意味がわかればもう忘れて結構です。🤗😺
次のプログラムを見てください。
cnt = 4
while cnt > 0:
print(cnt)
cnt -= 1
4
3
2
1
4 から始まるカウントを画面に出力してからカウントを 1 減らしてループし、やがてカウントが 0 になったら を抜けます。
今度は := を使って書いてみます。
cnt = 5
while cnt := cnt - 1:
print(cnt)
4
3
2
1
条件式を書くところに、代入式が書かれています。
cnt
の初期値を 5
にして、文の条件式を書く所で cnt
からまず 1
を引き、4
になった値を := の前の cnt
に入れます。
最終的に := の前の cnt
が条件式の評価にかけられ、cnt
の値が 4
なので True
に化けます。
条件式を書く所に代入文(=)は使えませんが、代入式(:=)なら使えます。
けど、この使い方では代入式の便利さがまだよくわからないと思います。
内包表記の中では代入文(=)が使えないというのは、すでにおわかりかと思います。
代入式を使うと、内包表記の中で代入文のような使い方ができるようになります。
a = [1, 2, 3, 4, 5]
b = [c for i in a if (c:=i*3)%2 == 0]
print(b)
[6, 12]
← 2✕3 と 4✕3 の偶数のみの結果これは、a
のリストの要素を文で順に i
に入れ、『 i
を3倍した数を c
に入れ、c
が偶数の時に c
の値をリストに作ります。』
一瞬「なるほどこれは便利!」と思うかもしれませんが、これは代入式を使わなくても、実際次のように書くこともできます。
a = [1, 2, 3, 4, 5]
b = [i*3 for i in a if i*3 % 2 == 0]
print(b)
[6, 12]
しかしこの方法だと i*3
を2回計算してしまいます。同じ処理を2回して、同じ値を2回作るのは無駄ですね。
それを代入式を使うことで1回の処理で済ませられるようになります。効率が良いということです。これが代入式の真髄です。
これは内包表記に限らず、ふつうの書き方をしても同じことが言えます。
いつもの書き方
a = [1, 2, 3, 4, 5]
b = []
for i in a:
if i*3 % 2 == 0:
b.append(i*3)
print(b)
代入式を使った書き方
a = [1, 2, 3, 4, 5]
b = []
for i in a:
if (c:=i*3) % 2 == 0:
b.append(c)
print(b)
予め c = i*3
としてから if c % 2 == 0:
とすることもできますが、代入式を使うとまとめて書けます。
仮に代入式の部分の処理が丸1日かかるとすると、いつもの書き方の場合は2日かかってしまうことになります。
上記のプログラムの例の場合でも「ほんの一瞬」と思うなかれ。このほんの一瞬の処理が一日に何十回何百回と繰り返され、それが何ヶ月何年と繰り返されることになる場合、処理をする際に CPU が使われますが、CPU が使われるということはその分の電力が消費されるということです。
無駄が1ヶ所だったり一台の PC だけというならともかく、もし無駄が何十ヶ所何百ヶ所あって、これらの処理が何億台の PC でされるとなったら、とんでもない電力の無駄遣いとなってしまいます。
いくらPCが省電力になっても、ソフトウェアがこんな状態ではエコでもなんでもありませんね。プログラムにおいても無駄を省くというのは本当に大切なことなんです。🌏
もし代入式を使うべき箇所を見つけたら積極的に使ってみてください。
これまでにも変数にアンダースコア _ をさりげなく使ってきていましたが、これの意味についてここではっきり説明します。
「値を使わない」という意味を具体的に説明します。と言っても難しいものではありません。
for _ in range(5):
print('Hello world!')
Hello world!
Hello world!
Hello world!
Hello world!
Hello world!
'Hello world!'
を 5回画面に出力するものですが、range(5) から渡される値を以降で使うことがありません。
アンダースコアの代わりに i
を使ってもよいのですが、ここで i
と記しておくと、以降のどこかで i
が使われていると考えられてしまう恐れがあります。
なぜよろしくないのか。
デバッグなどであとでプログラムを見返した時に、「この i
はどこでどんなことに使われているのだろう?」と、使われてもいない i
のための無駄な探索物語が始まってしまいます。
そのために for _ in range(5):
としておくことで、「ただ単に5回ループする」というだけの文ということがすぐにわかるようになります。
その他にも、
lst = [(101, 'ヒロ'), (102, '沙英'), (103, '乃莉'), (201, 'ゆの'), (202, '宮子'), (203, 'なずな')]
for _, name in lst:
print(name)
ヒロ
沙英
乃莉
ゆの
宮子
なずな
という様にタプルの使わない要素をアンダースコアに置くことができます。もちろんどこでも使えて何度でも使いまわすことができます。
これらはよく使う有効なやり方なので、恥ずかしがらずにどんどん使いましょう!😺
テンポラリ (temporary) とは「一時的」という意味です。
「値に重要な意味は無いんだけど、ちょっと変数に入れておきたい!」という時に tmp という変数名が使われます。
これも文の i
のように、大昔から使われている定番の変数です。
他に temp も使われています。
早い話が「使い捨て」の変数です。けど、もちろん再代入して何度でも使えます。
アンダースコアもテンポラリ変数として使われたりしますが、Python3 ではおすすめしません。値を使わない意味のアンダースコアと混同してしまうからです。
※ この場合のアンダースコアは「どんな値も受け入れる」という意味になります。知らなくていいけど。🐾
最も知られている使い方はおそらくこれでしょう。
nums = [2, 1, 3]
tmp = nums[0] # 一旦 tmp にnums[0] の 2 を逃がす
nums[0] = nums[1] # nums[1] の 1 を nums[0] に代入する
nums[1] = tmp # tmp に逃がしておいた 2 を nums[1] に代入する
print(nums)
[1, 2, 3]
これは値をスワップ(値交換)するプログラムなのですが、大昔から使われている一般的な方法です。
国家資格の「基本情報技術者」試験で学習するくらいのド定番アルゴリズムです。ミニチュア版 ハノイの塔 (Wikipedia) です。といってもこの程度のもの学ばずとも、誰でもその場で思いつくでしょと思うんですけどね。🤣
tmp に慣れると、重要な値でもその場で変数名を考えるのがめんどくさい時につい tmp を使いがちになったりします。
そのうち tmp1, tmp2・・・とか、さらに temp まで加わってカオスな状態になってしまいます。😽
これは頭の中でつくった流れを忘れてしまう前に早くプログラムにしたいのに、変数名を考えるという余計な作業のせいで忘れてしまいそうになるから、とりあえず今は tmp にしといて後でなんとかしようと思いつつ、後になってもなんともしないまま結果、用途不明なテンポラリ変数が散乱してしまうということになるわけです。
整理整頓清掃清潔(4S)ができない人の典型的な末路です。
そうならない為には pass を使いつつ、大まかな構成を作ってから細かい部分に取り掛かるというのも1つの手です。(pass の部分の作り忘れに注意!)
その他、アンダースコアをテンポラリ変数として使うのはおすすめしないと話しましたが、次の様な形で使われることはあります。
nums = [2, 1, 3]
max_ = max(nums)
print(max_)
3
単純に「最大値」という意味で max を変数にしたかったけど、関数のmax() と名前がかぶってしまうので、それを回避するために max_ にするという使い方はよく目にしますし、私自身もよく使います。いいのかよくないのかは実のところ、わかりかねますが。😓
これも気をつけないと max__ max___ max______________ となりかねないので注意!😸
ちなみにアンダースコアを名前の先頭に付けるのは禁止です。( _max とか)
ここでは説明しませんが、これにはきちんとした意味があってきちんとした使われ方がありますので避けてください。