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

同じオペランド、異なる結果

1

a = [1, 2, 3, 4]
b = a
a = a + [5, 6, 7, 8]

出力

>>> a
[1, 2, 3, 4, 5, 6, 7, 8]
>>> b
[1, 2, 3, 4]

2

a = [1, 2, 3, 4]
b = a
a += [5, 6, 7, 8]

出力

>>> a
[1, 2, 3, 4, 5, 6, 7, 8]
>>> b
[1, 2, 3, 4, 5, 6, 7, 8]

解説

  • a += ba = a + bは同じ動作をしない。
  • a + [5,6,7,8]は新しいオブジェクトを生成し、aに新しい参照をセットする。bの内容にはなんら変更がない。
  • a += [5,6,7,8]は実際にはextend関数であり、abは同じオブジェクトを指し示したまま、インプレースで変更が加えられる。

変更不能オブジェクトを変更する

some_tuple = ("A", "tuple", "with", "values")
another_tuple = ([1, 2], [3, 4], [5, 6])

出力

>>> some_tuple[2] = "change this"
TypeError: 'tuple' object does not support item assignment
>>> another_tuple[2].append(1000) #This throws no error
>>> another_tuple
([1, 2], [3, 4], [5, 6, 1000])
>>> another_tuple[2] += [99, 999]
TypeError: 'tuple' object does not support item assignment
>>> another_tuple
([1, 2], [3, 4], [5, 6, 1000, 99, 999])

タプルって変更できないはずでしたよね…

解説

変更不能なシーケンス型のオブジェクトは、一度生成されるとその値を変更することができません。 (オブジェクトに他のオブジェクトへの参照が入っている場合、参照されているオブジェクトは変更可能なオブジェクトでもよく、その値は変更される可能性があります; しかし、変更不能なオブジェクトが直接参照しているオブジェクトの集合自体は、変更することができません。)

  • +=はインプレースにリストを変更する。要素の割り当ては動作しないが、例外が発生した時点で、要素はインプレースに変更されている。

スコープ中に定義されていない変数を使う

a = 1
def some_func():
    return a

def another_func():
    a += 1
    return a

出力

>>> some_func()
1
>>> another_func()
UnboundLocalError: local variable 'a' referenced before assignment

解説

  • スコープ中で変数を割り当てたときに、その変数はローカル変数として扱われる。つまりaanother_func内ではローカルになっているのだが、初期化されていないためにエラーを投げる。
  • Python名前空間とスコープ解決についてもっと知りたい人はこの記事を参照
  • another_funcの外側のスコープの変数を変更したいときは、globalを使おう。
def another_func()
    global a
    a += 1
    return a

出力

>>> another_func()
2

外側のスコープから消える変数

e = 7
try:
    raise Exception()
except Exception as e:
    pass

出力(Python 2.x)

>>> print(e)
# prints nothing

出力(Python 3.x)

>>> print(e)
NameError: name 'e' is not defined

解説

ソース: https://docs.python.org/3/reference/compound_stmts.html#except

例外がasにより割り当てられると、その変数はexcept節の終わりに除去される。

以下のコードが

except E as N:
    foo

次のような感じに翻訳される。

except E as N:
    try:
        foo
    finally:
        del N

これが意味するところは、あるexcept節が終わったとき、例外はまた別の名前に割り当てるべきである、ということだ。

次の文章が理解できない。

Exceptions are cleared because, with the traceback attached to them, they form a reference cycle with the stack frame, keeping all locals in that frame alive until the next garbage collection occurs.

  • 上のexcept節は、Pythonではスコープ化されていない。この例における全ての変数は同じスコープにある。そして変数eexcept節の実行により除去される。分離された内部スコープを持つ関数においては、このようなことは起こらない。以下の例はそれを示している:
def f(x):
    del(x)
    print(x)

x = 5
y = [5, 4, 3]

出力

>>>f(x)
UnboundLocalError: local variable 'x' referenced before assignment
>>>f(y)
UnboundLocalError: local variable 'x' referenced before assignment
>>> x
5
>>> y
[5, 4, 3]
  • Python 2.xでは変数名eException()インスタンスに割り当てられる。これを印字しようとすると何も表示されない。
>>> e
Exception()
>>> print e
# Nothing is printed!

どこでreturnしても帰ってくる!

def some_func():
    try:
        return 'from_try'
    finally:
        return 'from_finally'

出力

>>> some_func()
'from_finally'

解説

  • return, breakまたはcontinue文がfinally節をもつtry中で実行されたとき、finally節も結局実行されることになる。
  • 関数の返り値は、最後に実行されたreturn文により決定される。finallyは常に最後に実行されるので、返り値は上述のようになる。

真が偽であるとき (Python 2.x系限定)

True = False
if True == False:
    print("I've lost faith in truth!")

出力

I've lost faith in truth!

解説

  • Pythonbool型を持たない。偽は0, 真は1で表している。TrueFalseはビルトイン変数なので、後方互換性のため長らく定数化することができなかった。
  • Python 3.xではこの問題は解決された。