わかったつもりのPython その2

python

 前回の続き。オライリーの読解です。O’Reilly Python 文法詳解

 普通に書いてたら理解できる文法の「きほんのき」は省いて「へぇ~」と思った所をピックアップしていこうと思います。前回の続きで4章~5章まで。細かい解説はしていません。「へぇ~」と思ったらそのキーワードでググるか本買ってください。

4章 シーケンスとコンテナ型

 いわゆる配列。これをPythonの世界ではシーケンスと呼ぶそうです。いわゆる配列とは異なり、色々種類特色のあるいろいろなものが用意されています。この章ではそれらの説明をしてくれています。じゃあコンテナってのは何なんじゃい。その説明がなかったです…。

 seq[index]の形式の添字によるアクセスから、seq[開始:終了]といったスライスによるアクセス、演算子の説明までそれぞれのシーケンスに対して解説してくれています。

 基本的にはぼーっと生きていてもそれなりに情報として入ってくるのですが、リストとタプルの違いや、触れたことがなかったbyte型や集合といったものまで解説されています。

 どれもfor文で要素を1つずつ取り出して処理できるようなイテラブルな子達です。また読解するうえでキーになるのが変更可能か否か。ミュータブルかイミュータブル。これをお勉強することができました。関数の説明も多いので辞書的な章ですが、なかなか「へぇ~」でした。

リストとタプル

 リストとタプル。あまり違いを意識せずに何気なく使っていましたが、ちゃんと違います。どんなオブジェクトでも自由に要素に加えられる点は同じ。クラシックなCだと考えにくい型の違う要素をごちゃまぜの配列にできてしまいます。初期化の違いは使うカッコが違うだけ。リストが[]でタプルが()です。

 気を付けたいのはどちらも要素はほかのオブジェクトへの参照です。外で書き変わるとその要素の中の値も書き換わります。下の例がそれです。

L = [0,1,2] # リスト
T = (0,1,2,L) # タプル
print(T)
  (0, 1, 2, [0, 1, 2])

L += [3,4]
print(T)
  (0, 1, 2, [0, 1, 2, 3, 4])

 Lが外で大きくなると、タプルの要素のLも大きくなっています。

 リストとタプルの違いとして、リストは変更可能(ミュータブル)でタプルは変更不能(イミュータブル)になります。上の例を見るとイミュータブルのタプルのくせに内容変わっとるやんけ。と思われるかもしれませんが、タプルの要素の参照先が変わってるだけでタプルの要素自体は書き換わっていません。同じ参照なので。あぁややこし。

 これを逆にすると

T = (0,1,2) # タプル
L = [0,1,2,T] # リスト
print(L)
  [0, 1, 2, (0, 1, 2)]

T += (3,4)
print(L)
  [0, 1, 2, (0, 1, 2)]

 TはタプルなのでリストLの参照先のタプルは外で何しようが変化してません。(T += […]の部分では新しいタプルオブジェクトが作られているってことのようです)

 まぁこんな違いがあるので気を付けましょ。変更不能で不便そうなタプルですがその利点は色々なところで述べられています。本書では

変更不能なシーケンスのメリットに、辞書のキーや集合の要素として使用できるという点があります。変更可能なリストは、辞書のキーや集合の要素には使用できません。

また、リストとタプルには、想定される使い方にも違いがあります。

リストの多くは要素がすべて同じ型です。決まったデータのリストを作成するのが一般的です。

タプルの場合、データベースのレコードのように、タプル全体でデータとして意味を持つような場合に使われます。(例えばユーザーID、姓、名のような記述)複数の要素から構成される値を意味する、Cの構造体のような使われ方が近いでしょう。

4.3.4 リストとタプル

 だそうです。個人的にはまだ実感はないです。「ふ~ん」な感じです。

 どちらもイテラブルな子なのでfor a in b:なbになりえる型です。個人的によく使うnumpy.arrayも含めて比較したコードが以下です。

L = [0,1,2,3] # List
T = (6,7,8,9) # tuple
NuA = np.array([6,7,8,9]) # Array of int32

# 添字でアクセスできる
print(L[0]) # 0
print(T[0]) # 6
print(NuA[0]) # 6

# イテラブルなのでfor文可能
for l in L:
    print(l) # 0,1,2,3
for t in T:
    print(t) # 6,7,8,9
for na in NuA:
    print(na) # 6,7,8,9

# ミュータブルなのは代入できる
L[0] = 1
# T[0] = 1  # TypeError: 'tuple' object does not support item assignment
NuA[0] = 9

# ミュータブルなのはスライスで上書きできる
L[:] = T
# T[:] = L # TypeError: 'tuple' object does not support item assignment
NuA[:] = L

# += で結合できる(同じオブジェクトが維持される)
LOrg = L
LL = [4,5]
L += LL
print(L) # [6, 7, 8, 9, 4, 5]
print(LOrg) # [6, 7, 8, 9, 4, 5]
if LOrg is L:
    print("same") # same
else:
    print("not same")

# += で結合できる(別オブジェクトへ変わる)
TOrg = T
TT = (10,11)
T += TT
print(T) # (6, 7, 8, 9, 10, 11)
print(TOrg) # (6, 7, 8, 9)
if TOrg is T:
    print("same")
else:
    print("not same") # not same

# += で結合できない
NNA = np.array([12,13])
#NuA = NuA + NNA # ValueError: operands could not be broadcast together with shapes (4,) (2,) 
NuA = np.append(NuA,NNA)
print(NuA) # [ 6  7  8  9 12 13]

 おもろいです。うまく使い分けたいところです。

 あといつも困るのがリスト内包記述。いつも書こうと思うとWeb調べて、2,3回間違えてを繰り返してしまいます。多分雰囲気だけで定義を理解できてないんだろうなぁ…。

[式 for 変数名 in イテラブルオブジェクト]

[式 for 変数名 in イテラブルオブジェクト if 条件式]

 これが定義のようです。入れ子にもできるようで、

[式 for 変数名 in イテラブルオブジェクト1
    for 変数名 in イテラブルオブジェクト2
        for 変数名 in イテラブルオブジェクト3 …

 だそうです。実例

years = [2022, 2023]
months = [1,2,3,4,5,6,7,8,9,10,11,12]
dst = [(y,m) for y in years for m in months]
print(dst)
----
[(2022, 1), (2022, 2), (2022, 3), (2022, 4), (2022, 5), (2022, 6), (2022, 7), (2022, 8), (2022, 9), (2022, 10), (2022, 11), (2022, 12), 
(2023, 1), (2023, 2),(2023, 3), (2023, 4), (2023, 5), (2023, 6), (2023, 7), (2023, 8), (2023, 9), (2023, 10), (2023, 11), (2023, 12)]

 ん~。自力で書けるかなぁ…。慣れだろうけど。

文字列とバイト列とbytearray型

 どいつもこいつも似たような奴らです。バイト単位の配列で文字列とバイト列は変更不能(イミュータブル)、バイトarrayは変更可能(ミュータブル)です。

 文字列はその性質上文字列を入れることに特化しており、文字列操作系の関数が充実しています。とても覚えきらないのでこの章は辞書として使うとします。こんなこといいな、できたらいいな、な関数は大体そろってそうです。

 バイト列とbytearray型はミュータブルかイミュータブルかの違い以外は大きな違いはなさそうで、関数もbytearray固有の関数もあるようですが、基本コンパチで行けるそうです。これも同じようにコードで比較するとこんな感じ。

S = "0123" # 文字列(str)
B = b"6789" # bytes
Ba = bytearray(B) # bytearray

# 添字でアクセスできる
print(S[0]) # 0
print(B[0]) # 54 ('6'の文字コード)
print(Ba[0]) # 54 ('6'の文字コード)

# イテラブルなのでfor文可能
for s in S:
    print(s) # 0,1,2,3
for b in B:
    print(b) # 54,55,56,57
for ba in Ba:
    print(ba) # 54,55,56,57

# ミュータブルなのは代入できる
#S[0] = "4" # TypeError: 'str' object does not support item assignment
#B[0] = b"9"[0] # TypeError: 'bytes' object does not support item assignment
Ba[0] = b"9"[0]

# += で結合できる(別オブジェクトへ変わる)
SSOrg = S
SS = "45"
S += SS
print(S) # 012345
print(SSOrg) # 0123
if SSOrg is S:
    print("same")
else:
    print("not same") # not same

# += で結合できる(別オブジェクトへ変わる)
BOrg = B
BB = b"1011"
B += BB
print(B) # b'67891011'
print(BOrg) # b'6789'
if BOrg is B:
    print("same")
else:
    print("not same") # not same

# += で結合できる(オブジェクトが維持される)
BAOrg = Ba
BBA = b"1011"
Ba += BBA
print(Ba) # bytearray(b'97891011')
print(BAOrg) # bytearray(b'97891011')
if BAOrg is Ba:
    print("same") # same
else:
    print("not same")

 先ほどのリスト・タプル間の違いと似た感じです。

 この章を読んでいて、色々「へぇ~」な関数の紹介とかTipsはあったのですが、こりゃいいと思った点を一つだけメモしておきます。バックスラッシュを多用するような文字列をコードの中で直書きする際に、\をエスケープするのが面倒なこと、ありますよね。その際にr’xxx’と、rを頭につけるとraw文字列という記述になって、エスケープが不要になるようです。あぁ便利

# a = '\user\local\bin\test' # SyntaxError: (unicode error) 'unicodeescape' codec can't decode bytes in position 0-1: truncated \uXXXX escape
a = '\\user\\local\\bin\\test'
b = r'\user\local\bin\test'
print(a) # \user\local\bin\test
print(b) # \user\local\bin\test

 これで置き換えられるようです。パスをコピペで動かしたいときとかわざわざ\を足してたのでその不便がなくなります。

辞書と集合

 辞書型ってのは使ったこともあるのですが、集合?恥ずかしながら今回この本を読んで初めてお目にかかりました。そういう意味ではbytesarrayとかも初めてでしたが想像ができました。しかしこの集合って型は初めて見たし、使い所がまたよくわからない。

 どちらもリテラルには{}このカッコを使います。なのでややこしいですが、

a = {}

 これは空の辞書になってしまいます。空の集合はset()って関数を使うのだそうです。

 辞書型(dict)に関してちょっとだけ。辞書型はキーと値をセットとして記憶させておく型で、キーにあたる部分はユニークである必要があります。キーに設定できるのは値が変更されない、いわゆるイミュータブルなオブジェクトに限られます。ここでイミュータブルなタプルや文字列が活躍するようです。

 ちょっと「へぇ~」だったのは、キーに型違いの同じ値を入れてもダメ見たいです。つまり整数の1と浮動小数点の1.0は同じキーとして解釈されるようです。

 辞書にも内包表記は定義されており、リストと同じように

{キー:値 for 変数名 in イテラブルオブジェクト}

 で作れるそうです。これはやったことないなぁ・・・。

 集合(set)も少し記述を残しておきます。集合は、同じ値の要素は1つだけしか登録できない、という性質を持つコレクションだそうです。登録済みの要素と同じ値の要素を追加しようとしても、追加されません。なので、

a = {0,1,2,3,1,2,3,1,2,1} # 重複したものは仲間に入れない
print(a)
-----
{0, 1, 2, 3}

 こうにしかなりません。

 またリストやタプルと同じように複数の異なる型を混在させられるようです。集合自体はミュータブルなのですが、辞書のキーと同じように登録できるのはイミュータブルなオブジェクトに限られます。辞書のキーだけの配列だと思うといいのかもしれません。

 この演算子の振る舞いがまたユニーク。名前が集合なのでそういう事か。という感じです。

 比較演算子で部分集合か否かを確認でき、ANDで重なる部分だけ、ORで両者をマージ。AND/ORはベン図を思い描くとよくわかります。

{1,2,3} < {1,2,3,4} # True
{1,3,8} < {1,2,3,4} # False

{1,2,3,4} & {3,4,5,6} # {3,4}

{1,2,3,4} | {3,4,5,6} # {1,2,3,4,5,6}

 frozenset型というイミュータブルな集合ってのもあるようです。

 これまでと同じように宣言、アクセス、for文、結合と試してみます。アクセスのやり方がこれまでと異なりトリッキーですが想定通りの動きです。

D = {'a' : 0, 'b' : 1, 'c' : 2, 'd' : 3} # dict
Se = {0,1,2,3} # set

# キーでアクセスできる
#print(D[0]) # KeyError: 0
print(D['a']) # 0
#print(Se[0]) # TypeError: 'set' object is not subscriptable

# イテラブルなのでfor文可能
for d in D:
    print(D[d]) # 0,1,2,3
for se in Se:
    print(se) # 0,1,2,3

# ミュータブルなのは代入できる
D['a'] = 4
#SE[0] = 0 # TypeError: 'set' object does not support item assignment

# 結合できる(オブジェクト維持)
DOrg = D
DD = {'e' : 4, 'f' : 5}
# D = D + DD # TypeError: unsupported operand type(s) for +: 'dict' and 'dict'
D.update(DD)
print(D) # {'a': 4, 'b': 1, 'c': 2, 'd': 3, 'e': 4, 'f': 5}
print(DOrg) # {'a': 4, 'b': 1, 'c': 2, 'd': 3, 'e': 4, 'f': 5}
if DOrg is D:
    print("same") # same
else:
    print("not same")

# 結合できる(オブジェクト維持)
SEOrg = Se
SSE = {4, 5}
#Se = Se + SSE # TypeError: unsupported operand type(s) for +: 'set' and 'set'
Se |= SSE
print(Se) # {0, 1, 2, 3, 4, 5}
print(SEOrg) # {0, 1, 2, 3, 4, 5}
if SEOrg is Se:
    print("same") # same
else:
    print("not same")

5章 ステートメント

 この章ではifやforといったベーシックな命令文の説明で、章自体も短いです。ほかの言語を知っていればそれほど目新しいものはないですが、そんな章でも何点か「へぇ~」なポイントがありました。

pass文

 何もしない。って文です。何に使うんだ?と思いますが、if文の受け答えに使うことが多いようです。

if a:
    pass: # 何もしない 空行にできない。
else:
    print("not a")

 if文の受け答えは空行にできないルールなので、このようなシチュエーションで使うものです。

 if以外にもwhileやdefでも同じです。

else節

 何をいまさらと思うかもしれませんが、if文のそれではないです。for文やwhile文のelseです。ループがbreakされなかった場合(ふつーに回り切った場合)に実行されます。説明するよりコード見た方がいいですね。

total = 0
for i in item:
    if i < 0:
        break
    total += i
else:
    print(total) # breakされなかったら実行される。

 これと同じことしようと思ったらflagを立ててbreakしたか否か記録して、そのflagを確認するif文を入れないといけないので、確かに使いでがあるかもしれません。

 ついでにfor文で面白かったのが、for文の中で複数の変数群をリストで受け取ることができるようです。また*の記述です。色々複数束ねる系でよく出てきます。覚えておきましょう。

L = [(1,2,3), (4,5,6), (7,8,9)]
for i in L:
    print(i) # (1,2,3), (4,5,6), (7,8,9)
    
for i, *j in L:
    print(j) # [2,3], [5,6], [8,9]


raise文

 何だこりゃと思いましたが、例外の送出。つまりthrowです。Pythonではraiseで投げるらしいです。try catchの仕組み自体はオーソドックスな感じですが、throwではないみたいです。

assert文

 これも何じゃこりゃでした。これは実行時にデータや条件の整合性チェックで使われる文で、問題が起こると例外を投げるraiseするようです。

 エラー処理の代わりか?と思いきやPythonの実行方法次第ではこの文は無視されるようなので、本来あるべきエラー処理にはこのassert文を使うべきではないです。デバッグ用途ですかね。この値で来るはずがないけど一応見張っとく。とかそんな趣旨で使う文ですかね。

with文

 with文で前処理を行い、withブロックを抜けたときに後処理をしてくれる。というお助け文のようです。処理に一定のルールが必要になります。このルールにのっとったインターフェースを持つオブジェクトをコンテクストマネージャと呼ばれるようです。

 主にファイル操作で登場することが多いらしいです。通信でも使われるみたい。

つづく

 ひとまず5章まで終わりました。6章へ続く。

python
スポンサーリンク
キャンプ工学

コメント

タイトルとURLをコピーしました