koji/メガネ男の日誌

日々の学び、活動状況を記録します。仕事のことは少なめ。

Effective Python(第2版) 2章~3章の気になった箇所をまとめてみた

f:id:kj_man666:20200906100859j:plain

引き続き、Challeng-Every-Monthというコミュニティで、みんなで Effective Python を読み合うイベントに参加しています。

Effective Pythonの公式の .py コードがGitHubに公開されています。

しばらくバタバタしていて、2章~3章はすべてのコードを詳細に見れていません。

勉強のため、気になったコード、個人的に使えそうだなあと思ったコードを啄んでいます。

手前ミソですが、第1章の記事はこちらです。

2章 項目13 アンパック

変数に * (アスタリスク)を付けてアンパックすることで、複数の変数にリストの値をまとめて代入するとき、リスト内の要素数が変数の数を上回っても代入ことができる。

# 経過年数を表すリスト
age_list = [0, 9, 4, 8, 7, 20, 19, 1, 6, 15]

# 並び替え
sorted_list = sorted(age_list, reverse=True)
sorted_list

そのまま複数の変数にまとめて値を代入すると、変数が2つしかないのにリスト内には3つ以上の数値があるため、エラーが発生する。

リストから1番目、2番目に大きい数値を抽出したい

bigest, second_bigest = sorted_list

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-14-6f07495bfa6c> in <module>
      1 # 1番目、2番目に大きいものを抽出
      2 # エラーになる
----> 3 bigest, second_bigest = sorted_list

ValueError: too many values to unpack (expected 2)

スライスで抽出することも可能だが、コードがゴチャついてしまう

bigest = sorted_list[0]

second_bigest = sorted_list[1]

print(f"bigest: {bigest}, second_bigest: {second_bigest}")

[出力] bigest: 20, second_bigest: 19

アンパックを使うと、スライスを使うよりも行数を少なく処理できる。

bigest, second_bigest, *others = sorted_list

print(f"bigest: {bigest}, second_bigest: {second_bigest}, others: {others}")

なお、アンパックのみでデータを受け取ることはできない。

*others = sorted_list

  File "<ipython-input-104-83e6b9fe5a33>", line 5
SyntaxError: starred assignment target must be in a list or tuple

辞書型のデータもアンパック可能

# 用意した辞書型データ
info = {
    'age':(20, 19, 15, 9, 8, 7, 6, 4, 1, 0),
    'type':("a", "b", "c", "d", "e", "f", "g", "h", "i", "j")
}

# アンパック処理
((key1, (value1, *value_others1)),
 (key2, (value2, *value_others2))) = info.items()

# 結果を出力
print(f"No.1 key {key1}: values {value1}, values_others {value_others1}")
print(f"No.2 key {key2}: values {value2}, values_others {value_others2}")

[出力]
key age: values 20, values_others [19, 15, 9, 8, 7, 6, 4, 1, 0]
key type: values a, values_others ['b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']

イテレータをアンパックすることも可能

def generate():
    yield ("a", "b", "c")
    yield ("d", "e", "f")
    yield ("g", "h", "i")
    yield ("j", "k", "l")
it = generate()

# イテレータのアンパック
first, *others = it
print(f"first: {first}, others: {others}")

[出力] first: ('a', 'b', 'c'), others: [('d', 'e', 'f'), ('g', 'h', 'i'), ('j', 'k', 'l')]

ちなみに、スライスを使って処理することも可能

# listに変換する必要がある
it_list = list(generate())

print(f"first: {it_list[0]}, others: {it_list[1:]}")

[出力] first: ('a', 'b', 'c'), others: [('d', 'e', 'f'), ('g', 'h', 'i'), ('j', 'k', 'l')]

3章 項目22 可変長位置引数、スター引数

アンパックを使うと、代入するものが何もなくても、空のリストを自動で代入してくれる

# 1つしか要素のないリストを用意する
num_list2 = [5]

# othersに代入するリストはないので、自動的に空っぽのリストが入る
first, *others = num_list2
print(f"first: {first}, others: {others}")

[出力] first: 5, others: []

スライスを使う場合は、空のリストを指定する必要が生じる

first, others = num_list[0], []

空のリストを指定しないとエラーが生じる

first, others = num_list[0]

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-112-99477d836b96> in <module>
----> 1 first, others = num_list[0]

TypeError: cannot unpack non-iterable int object

引数であるvalue が空っぽの場合はメッセージのみを返す log()という関数を前提とし、value が空の時、どのような処理になるか確かめる

def log(message, value):

    if not value:
        print(message)
    else:
        print(message, value)

引数であるvalue に値を代入する場合

log('Number is', [1, 2])

[出力] Number is [1, 2]

引数であるvalue に値を代入しない場合、エラーが生じる

log('Number is Nothing')

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-64-3e547894f1e6> in <module>
----> 1 log('Number is Nothing')

TypeError: log() missing 1 required positional argument: 'value'

引数である value に値を代入しない場合は valueに空のリストを指定する必要がある

log('Number is Nothing', [])

[出力] Number is Nothing

引数にスター(*)を付けることで引数に代入する値がなくても自動でからのリストが代入される

def log(message, *value):
    # values が空っぽの場合はメッセージのみを返す
    if not value:
        print(message)
    else:
        print(message, value)
log('Number is Nothing')

[出力] Number is Nothing

ただし、引数にスター(*) を付けてしまうと、本来意図しない引数として認識されてしまうこともある

先ほどのlog関数に、sequenceという引数を1番目に追加し、出力にも追加する

なお、sequenceは数値データを想定している

def log2(sequence, message, *value):
    # values が空っぽの場合はメッセージのみを返す
    if not value:
        print(sequence, message)
    else:
        print(sequence,"-", value, message, )

7以降の値は引数valueにアンパックされる

log2(1, 'Number is', 7, 13)

[出力] Number is 1 - (7, 13)

引数であるsequenseになにも設定しなかった場合、本来意図しない引数に代入されてしまう

log2('Number is', 7, 13)

[出力] 7 Number is - (13,)

'Number is' が sequenseに、7 がmessageに、13 が value に格納されてしまい、出力がおかしなことになる

なぜこのような処理になるのか原因が突き止めにくいという欠点もある

上記のコードはこちらのPart 2フォルダの中、Chapter 02_03 Select.ipynbになります。

以上になります、最後までお読みいただきありがとうございました。