Pythonのどうしてこうなるの?トリッキーコード集 (part 6)

午前0時は存在しない (Python 3.5以前限定)

from datetime import datetime

midnight = datetime(2018, 1, 1, 0, 0)
midnight_time = midnight.time()

noon = datetime(2018, 1, 1, 12, 0)
noon_time = noon.time()

if midnight_time:
    print("Time at midnight is", midnight_time)

if noon_time:
    print("Time at noon is", noon_time)

出力

('Time at noon is', datetime.time(12, 0))

午前0時が表示されない。

解説

  • Python 3.5以前では、datetime.timeオブジェクトでの午前0時(UTC)のブール値はFalseと判定されていた。これはエラーを起こしやすいということで修正されている。

ブール値数え上げ

# A simple example to count the number of boolean and
# integers in an iterable of mixed data types.
mixed_list = [False, 1.0, "some_string", 3, True, [], False]
integers_found_so_far = 0
booleans_found_so_far = 0

for item in mixed_list:
    if isinstance(item, int):
        integers_found_so_far += 1
    elif isinstance(item, bool):
        booleans_found_so_far += 1

出力

>>> booleans_found_so_far
0
>>> integers_found_so_far
4

解説

  • ブール型はintのサブクラスである。
>>> isinstance(True, int)
True
>>> isinstance(False, int)
True

カッコの無駄骨

t = ('one', 'two')
for i in t:
    print(i)

t = ('one')
for i in t:
    print(i)

t = ()
print(t)

出力

one
two
o
n
e
tuple()

('one')はタプルになることを期待していた。どうしたらできるか?

解説

  • t = ('one',)あるいはt = 'one',でできる。
  • 単体の()tupleを表す特別な構文である。

変更から回復するループ変数

for i in range(7):
    print(i)
    i = 10

出力

0
1
2
3
4
5
6

ループが一度しか実行されない、とか思わなかっただろうか。

逆にこのループが正常に実行されるのはなぜか。

解説

これの動作、予想できるかな?

a, b = a[b] = {}, 5

出力

>>> a
{5: ({...}, 5)}

解説

(target_list "=")+ (expression_list | yield_expression)

そして

代入文は式のリスト (これは単一の式でも、カンマで区切られた式リストでもよく、後者はタプルになることを思い出してください) を評価し、得られた単一の結果オブジェクトをターゲット (target) のリストに対して左から右へと代入してゆきます。

  • target_list "=")+における+は1つ以上のターゲットリストが存在することを意味している。上のコードの場合、ターゲットリストはa, bそしてa[b]である(対して式リストは厳密に1つだけである。このケースでは{}, 5がそれだ)。
  • 式リストが評価されると、その値はターゲットリストの左から右へアンパックされる。このケースでは、タプル{}, 5a, bにアンパックされ、a = {}, b = 5となる。
  • a{}は代入されており、{}は可変リストである。
  • 左から2番目のターゲットリストはa[b]である(ここまでの知識がなければ、これはabも宣言していないのでエラーになると推測されるかもしれないが、abには確かに値が代入されている)。
  • いま、辞書のキー5({}, 5)にセットして、循環参照を生成する(出力中の{...}aが参照しているオブジェクトと同じものを参照している)。よりシンプルな循環参照の例を挙げると:
>>> some_list = some_list[0] = [0]
>>> some_list
[[...]]
>>> some_list[0]
[[...]]
>>> some_list is some_list[0]
[[...]]

これは最初の例と似通っている(a[b][0]aと同じオブジェクト)

  • 簡単にすると、コードを次のように腑分けすることができる。
a, b = {}, 5
a[b] = a, b

循環参照であることはa[b][0]aと同じオブジェクトであることから確かめられる。

>>> a[b][0] is a
True

ここまで。