SimplePrograms - Python Wiki
https://wiki.python.org/moin/SimplePrograms
の 10番目のプログラムは、Time, 条件, from..import, for..else です。
from time import localtime
activities = {8: 'Sleeping',
9: 'Commuting',
17: 'Working',
18: 'Commuting',
20: 'Eating',
22: 'Resting' }
time_now = localtime()
hour = time_now.tm_hour
for activity_time in sorted(activities.keys()):
if hour < activity_time:
print (activities[activity_time])
break
else:
print ('Unknown, AFK or sleeping!')
これも、10行ではないですが、activities という辞書を定義するときに、
インデントして継続行になっているところを1行と数えると10行です。
もう長くなったので、
丸ごとファイルに入れて、先頭行に #!/usr/bin/python3 を付加して、chmod u+x で実行可能にします。
そのファイル名を、prog-010.py とすると実行結果はこうなります。
先にdate コマンドを使ったのは、時間を扱うプログラム?だから現在の時間を知るためです。
$ date
2024年 4月 28日 日曜日 07:11:05 JST
$ ./prog-010.py
Sleeping
$
実行時刻を替えてもう一つ実行結果を記録しておきます。
$ date
2024年 4月 29日 月曜日 22:20:34 JST
$ ./prog-010.py
Unknown, AFK or sleeping!
$
さっそくプログラムの中身を見てみましょう。
まず from ですが、
https://docs.python.org/ja/3/reference/simple_stmts.html#import
で、import文の一つの形式として説明されています。
「from 節で指定されたモジュールを見付け出し、必要であればロードし初期化する;
import 節で指定されたそれぞれの識別子に対し以下の処理を行う:」
要するに、
from foo(モジュール名とします) import attr(識別子とします。)
によって、 foo.attr を省略して attr として表せるという指定ですね。
from節でロードするモジュール time は「時刻に関するさまざまな関数を提供」するものですね。
https://docs.python.org/ja/3/library/time.html?highlight=time
これによって、time.localtime() を省略して localtime() として使えて、
「エポックからの経過時間で表現された時刻を、」「ローカル時間に変換します。」
なので、現在の時刻が得られるようです。早速試して見ましょう。
>>> from time import localtime
>>> c = localtime()
>>> c # localtime()の出力(戻り値)
time.struct_time(tm_year=2024, tm_mon=5, tm_mday=2, tm_hour=23, tm_min=42, tm_sec=41, tm_wday=3, tm_yday=123, tm_isdst=0)
>>>
>>> c.tm_hour # c から tm_hour の値を得る
23
>>> c.tm_year # c から tm_year の値を得る
2024
>>> c.tm_year + 3 # c.tm_year の値は 文字列ではなくて数値
2027
>>> localtime().tm_hour # 8時になって localtime() を使うと 8 になっている。
8
>>>
というわけで、プログラムの hour は今の時刻を数値で表したものです。
辞書 activities の キーはこの時刻を表しているのですね。キーが24時間表記なので hour も0 ~ 23 ですね。
ここで、辞書に対する keys() メソッドを調べてみましょう。
>>> a = {5: 'test5', 2: 'test2', 9: 'test9' } # 辞書 a を定義
>>> a # a を表示する。
{9: 'test9', 2: 'test2', 5: 'test5'}
>>> a.keys() # a.keys() を表示する。
dict_keys([9, 2, 5])
>>> sorted(a.keys()) # これをソートする。
[2, 5, 9]
>>>
dict_keys() を sorted()の引数として渡すと、キーのソートされた配列が得られることが分かりました。
つまり、for文で activity_time にソートされたキーから生成された配列から一つづつキーを代入して、ループするのですね。
10番目のプログラムで再び条件式がでてきました。といっても、他のプログラムとほぼ同じだと思うので、
if 文の中の条件式は、hour(現在の時刻) が activity_time より小さかったら 真 となって、
下にインデントされた部分を実行するのですね。
真なら print()で辞書からそのキーに対応する値を表示したあと、break がでてきました。
https://docs.python.org/ja/3/reference/simple_stmts.html#the-break-statement
「break 文は、文を囲う最も内側のループを終了させ、ループにオプションの else 節がある場合にはそれをスキップします。」
なので、
for ループを終了して、その activity_time は保持されるわけですね。
さて、for ループの後ろに インデントのない else: が出てくるのは何でしょうか。
それが、プログラムの説明にある for..else だと思います。
https://docs.python.org/ja/3/reference/compound_stmts.html#the-for-statement
の定義では、
for target_list in expression_list : suite [ else : suite ]
となっているので、
for ループには else: 節を付けることができて、今までは省略されたいたということが分かりました。Python 以外はあまりでてこない?形式です。
「その後、スイートが実行されます。 全ての要素を使い切ったとき (シーケンスが空であったり、イテレータが StopIteration 例外を送出したときは、即座に)、 else 節があればそれが実行され、ループは終了します。」
「最初のスイートの中で break 文が実行されると、 else 節のスイートを実行することなくループを終了します。 」
なので、break 文を使っているので else節をスキップしてループを終了するのですね。
条件を満たさず、すべての要素を使い切ると for ループが終了して、最後にelse節が実行されるわけです。
試してみます。
>>> a
{9: 'test9', 2: 'test2', 5: 'test5'}
>>> for i in sorted(a.keys()):
... if i > 4 :
... print(a[i])
... else:
... print('Finish!')
...
test5
test9
Finish!
>>>
for文の中にbreke文がないのですべてのキーを調べたあとに else: 節を実行しています。
>>> for i in sorted(a.keys()):
... if 6 < i:
... print(a[i])
... break
... else:
... print('Finish!')
...
test9
>>>
サンプルプログラムのように break文を入れると else: 節はスキップしているのが分かります。
>>> for i in sorted(a.keys()):
... if 100 < i:
... print(a[i])
... break
... else:
... print('Finish!')
...
Finish!
>>>
該当するキーがないと、ループの中はなにもせずに else: 節を実行するのがわかります。
通常のプログラミング言語では else:節がないので、for ループで break してもfor ループの次の行を実行します。
Python では、 for文の else:節は break と併用すると便利と覚えておきます。
https://wiki.python.org/moin/SimplePrograms
の 10番目のプログラムは、Time, 条件, from..import, for..else です。
from time import localtime
activities = {8: 'Sleeping',
9: 'Commuting',
17: 'Working',
18: 'Commuting',
20: 'Eating',
22: 'Resting' }
time_now = localtime()
hour = time_now.tm_hour
for activity_time in sorted(activities.keys()):
if hour < activity_time:
print (activities[activity_time])
break
else:
print ('Unknown, AFK or sleeping!')
これも、10行ではないですが、activities という辞書を定義するときに、
インデントして継続行になっているところを1行と数えると10行です。
もう長くなったので、
丸ごとファイルに入れて、先頭行に #!/usr/bin/python3 を付加して、chmod u+x で実行可能にします。
そのファイル名を、prog-010.py とすると実行結果はこうなります。
先にdate コマンドを使ったのは、時間を扱うプログラム?だから現在の時間を知るためです。
$ date
2024年 4月 28日 日曜日 07:11:05 JST
$ ./prog-010.py
Sleeping
$
実行時刻を替えてもう一つ実行結果を記録しておきます。
$ date
2024年 4月 29日 月曜日 22:20:34 JST
$ ./prog-010.py
Unknown, AFK or sleeping!
$
さっそくプログラムの中身を見てみましょう。
まず from ですが、
https://docs.python.org/ja/3/reference/simple_stmts.html#import
で、import文の一つの形式として説明されています。
「from 節で指定されたモジュールを見付け出し、必要であればロードし初期化する;
import 節で指定されたそれぞれの識別子に対し以下の処理を行う:」
要するに、
from foo(モジュール名とします) import attr(識別子とします。)
によって、 foo.attr を省略して attr として表せるという指定ですね。
from節でロードするモジュール time は「時刻に関するさまざまな関数を提供」するものですね。
https://docs.python.org/ja/3/library/time.html?highlight=time
これによって、time.localtime() を省略して localtime() として使えて、
「エポックからの経過時間で表現された時刻を、」「ローカル時間に変換します。」
なので、現在の時刻が得られるようです。早速試して見ましょう。
>>> from time import localtime
>>> c = localtime()
>>> c # localtime()の出力(戻り値)
time.struct_time(tm_year=2024, tm_mon=5, tm_mday=2, tm_hour=23, tm_min=42, tm_sec=41, tm_wday=3, tm_yday=123, tm_isdst=0)
>>>
>>> c.tm_hour # c から tm_hour の値を得る
23
>>> c.tm_year # c から tm_year の値を得る
2024
>>> c.tm_year + 3 # c.tm_year の値は 文字列ではなくて数値
2027
>>> localtime().tm_hour # 8時になって localtime() を使うと 8 になっている。
8
>>>
というわけで、プログラムの hour は今の時刻を数値で表したものです。
辞書 activities の キーはこの時刻を表しているのですね。キーが24時間表記なので hour も0 ~ 23 ですね。
ここで、辞書に対する keys() メソッドを調べてみましょう。
>>> a = {5: 'test5', 2: 'test2', 9: 'test9' } # 辞書 a を定義
>>> a # a を表示する。
{9: 'test9', 2: 'test2', 5: 'test5'}
>>> a.keys() # a.keys() を表示する。
dict_keys([9, 2, 5])
>>> sorted(a.keys()) # これをソートする。
[2, 5, 9]
>>>
dict_keys() を sorted()の引数として渡すと、キーのソートされた配列が得られることが分かりました。
つまり、for文で activity_time にソートされたキーから生成された配列から一つづつキーを代入して、ループするのですね。
10番目のプログラムで再び条件式がでてきました。といっても、他のプログラムとほぼ同じだと思うので、
if 文の中の条件式は、hour(現在の時刻) が activity_time より小さかったら 真 となって、
下にインデントされた部分を実行するのですね。
真なら print()で辞書からそのキーに対応する値を表示したあと、break がでてきました。
https://docs.python.org/ja/3/reference/simple_stmts.html#the-break-statement
「break 文は、文を囲う最も内側のループを終了させ、ループにオプションの else 節がある場合にはそれをスキップします。」
なので、
for ループを終了して、その activity_time は保持されるわけですね。
さて、for ループの後ろに インデントのない else: が出てくるのは何でしょうか。
それが、プログラムの説明にある for..else だと思います。
https://docs.python.org/ja/3/reference/compound_stmts.html#the-for-statement
の定義では、
for target_list in expression_list : suite [ else : suite ]
となっているので、
for ループには else: 節を付けることができて、今までは省略されたいたということが分かりました。Python 以外はあまりでてこない?形式です。
「その後、スイートが実行されます。 全ての要素を使い切ったとき (シーケンスが空であったり、イテレータが StopIteration 例外を送出したときは、即座に)、 else 節があればそれが実行され、ループは終了します。」
「最初のスイートの中で break 文が実行されると、 else 節のスイートを実行することなくループを終了します。 」
なので、break 文を使っているので else節をスキップしてループを終了するのですね。
条件を満たさず、すべての要素を使い切ると for ループが終了して、最後にelse節が実行されるわけです。
試してみます。
>>> a
{9: 'test9', 2: 'test2', 5: 'test5'}
>>> for i in sorted(a.keys()):
... if i > 4 :
... print(a[i])
... else:
... print('Finish!')
...
test5
test9
Finish!
>>>
for文の中にbreke文がないのですべてのキーを調べたあとに else: 節を実行しています。
>>> for i in sorted(a.keys()):
... if 6 < i:
... print(a[i])
... break
... else:
... print('Finish!')
...
test9
>>>
サンプルプログラムのように break文を入れると else: 節はスキップしているのが分かります。
>>> for i in sorted(a.keys()):
... if 100 < i:
... print(a[i])
... break
... else:
... print('Finish!')
...
Finish!
>>>
該当するキーがないと、ループの中はなにもせずに else: 節を実行するのがわかります。
通常のプログラミング言語では else:節がないので、for ループで break してもfor ループの次の行を実行します。
Python では、 for文の else:節は break と併用すると便利と覚えておきます。
#!/usr/bin/env python3
from time import localtime
activities = {8: 'Sleeping',
9: 'Commuting',
17: 'Working',
18: 'Commuting',
20: 'Eating',
22: 'Resting' }
time_now = localtime()
hour = time_now.tm_hour
if __name__ == '__main__':
res = [activities[activity_time]\
for activity_time in sorted(activities.keys())\
if hour < activity_time]
print('Unknown, AFK or sleeping!' if res == [] else res[0])
やっぱリスト内包表記の出番で、かつ、条件に合わないブツをフィルタリングしちゃう。
そうすれば得られたリストの第0要素が求めるモノとなるんで、そいつを印字、にするんじゃないかな。
ただし、リスト内包表記なんかは「必ずリストを最後まで走査する」わけで、元々のコードの「条件に見合ったブツが見つかった時点で計算から脱出する」、つまり後続の計算をスキップする辺りがより効率的だろう、と言うのはその通り、だとは思います。リスト内包表記だと「計算を途中で中断する」トリックが仕込みづらい。
やっぱこの辺は悩みドコロにはなりますね。
コメントから、「必ずリストを最後まで走査する」ではなくて、途中で break すれば効率的なので”必要”という狙いが for ~ else にあると思いました。
ちょっと検索したら、Pythonプログラムを高速化するための Cython だと、この break を goto に変換しているみたいです。
Python は 「goto」 が無い!いや、拒否する!その代わりこれね、と言うのがこの仕様の示す折衷案でしょうか。興味深いです。
もっとも、「必ずリストを最後まで走査する」ことは、CUDA等が一般的になってきているのでハードによってはむしろ効率的となっている?みたいなので、当方で扱っているRaspberry Pi のような低速なハードの時は、依然として有用な仕様と捉えるべきかもしれませんね。
うん、僕もそう思います。
> Python は 「goto」 が無い!いや、拒否する!その代わりこれね、と言うのがこの仕様の示す折衷案でしょうか。
と言うか、基本的に例えばC言語なんかでも、gotoあっても使わせない為に(笑)、breakなんかが入ってる、ってのはありますからね。
breakは代表的な構文糖衣(Syntactic Sugar)だとは思います。
> 依然として有用な仕様と捉えるべきかもしれませんね。
そうかもしれません。
ところで、別解として、いつぞやのジェネレータ式を使う、ってのも考えられるんですが・・・。
#!/usr/bin/env python3
from time import localtime
activities = {8: 'Sleepint',
9: 'Commuting',
17: 'Working',
18: 'Commuting',
20: 'Eating',
22: 'Rasting'}
time_now = localtime()
hour = time_now.tm_hour
if __name__ == '__main__':
res = lambda : (activities[activity_time] for activity_time\
in sorted(activities.keys()) if hour < activity_time)
print(next(res()) if any(res()) else 'Unknown, AFK or sleeping!')
ジェネレータ式はそのままだと「未評価」なんで、anyで「真」の条件を発見した次第で以降の計算をスキップします。
だから題意的には「計算をスキップする」ので意図は意図通り動くんですが・・・。
一方、ジェネレータには以前見た通り、「要素を消費する」性質がある。anyで「真」を発見した時点でその「真」の要素を消費しちまうんですね。
結果、上のコードだと、仮に変数resでラムダ式を被してサンクにしなかった場合、例えば(18で)'Commuting'が真、って発見された時点でnext(res)は必ず'Eating'になってしまうと言う・・・・・・。
結果、やっぱりサンクにしておいて、ジェネレータ式が返す「一番最初の要素」を得る時、nextで同じ値を得る為に再計算せなアカン、と言う非常に美味しくない状況になります(笑)。
やっぱ悩ましいです、この問題は(笑)。考え方が「その通り」だとしてもどうもスッキリしません(笑)。コードと言うか「考え方」がコンピュータサイエンス寄りになって複雑化してきます。