【読書メモ】Python チュートリアル
概要
Pythonの公式チュートリアルを読んだ際のメモを残していく。読んでいるときのPythonのバージョンは3.10.6。
モチベーション
Pythonについて体系的に勉強したことがないことがコンプレックスなため。
引数の受け渡し
引数はsys.argv
という名前のリストに格納される。
ソースコードの文字コード
デフォルトでは、ソースコードはUTF-8でエンコードされているものとして扱われる。
標準ライブラリはASCIIで書かれている。
UTF-8以外の文字コードを使いたいとき
以下のようにファイルの先頭に書けばよい。
# -*- coding: cp1252 -*-
cp1252
の部分は、codecsがサポートしている文字コードであれば、なんでもOK。
for文で反復中のコレクションオブジェクトを変更する方法
作戦1:反復する前にコレクションオブジェクトをコピーする
# コレクションオブジェクト
users = {'Hans': 'active', 'Éléonore': 'inactive', '景太郎': 'active'}
for user, status in users.copy().items(): # ここでコピーしてしまう
if status == 'inactive':
del users[user] # 安全に削除できる
作戦2:新しいコレクションオブジェクトを作る
# コレクションオブジェクト
users = {'Hans': 'active', 'Éléonore': 'inactive', '景太郎': 'active'}
active_users = {} # 新しいコレクションオブジェクト
for user, status in users.items():
if status == 'active':
active_users[user] = status # 新しいコレクションオブジェクトに挿入
range()
forでよく使うrange()、あれは実はrange型のオブジェクトを生成する関数。
ずっとリストを返す関数だと思ってた...。
forとelseの組み合わせ
try-catchのように、forとelseの組み合わせ技があるらしい。
for n in range(2, 10):
for x in range(2, n):
if n % x == 0:
print(n, 'equals', x, '*', n//x)
break
else:
print(n, 'is a prime number') # 2つ目のforがbreakで終了したときは実行されない
Cのswitch文っぽいmatch-case文
当てはまった条件のみが実行される。
途中の未定義に見えるx
やy
は、x, y = point
のようなアンパックが行われたものとして使うことができる。
class Point:
x: int
y: int
def where_is(point):
match point:
case Point(x=0, y=0):
print("Origin")
case Point(x=0, y=y):
print(f"Y={y}")
case Point(x=x, y=0):
print(f"X={x}")
case Point():
print("Somewhere else")
case _: # _は全てにマッチする
print("Not a point")
where_is("hoge") # "Not a point"
p = Point()
where_is(p) # "Somewhere else"
p.y = 1
where_is(p) # Y=1
|
(or)を使うことで、複数条件も記載可能。
case 401 | 403 | 404:
return "Not allowed"
条件のネストもできる。
match points: # pointsはPointのリストを想定
case []:
print("No points")
case [Point(0, 0)]:
print("The origin")
case [Point(x, y)]:
print(f"Single point {x}, {y}")
case [Point(0, y1), Point(0, y2)]:
print(f"Two on the Y axis at {y1}, {y2}")
case _:
print("Something else")
global
global
変数がグローバル変数であることを明示するための文。
globalを使わないと上手く動かない例
g_var = "global" # グローバル変数
def change_global_var():
g_var = "local" # ここでグローバル変数を変更しているつもりだが...
change_global_var()
print(g_var) # 結果:global。localじゃない!!
globalを使うことで上記の問題を解決した例
g_var = "global" # グローバル変数
def change_global_var():
global g_var # ここでg_varがグローバル変数を指すことを明示
g_var = "local" # グローバル変数を変更
change_global_var()
print(g_var) # 結果:local。意図した通りに動いている。
nonlocal
1個外側のスコープの変数を参照することを明示する。
Pythonでの値渡し
実はここでの値とは、オブジェクトの参照のことであってオブジェクトそのものの値ではない。
イミュータブルな変数の値渡し
イミュータブルな値は演算ごとに結果を格納する場所が異なるので、その値が変わると変数も参照先も変える。そのため、以下のコードのように関数内で引数の値を変更しても、その影響は関数外には及ばない。なお、到達不能になった値はガベージ・コレクションによりメモリから削除される。
def change_arg(x):
x += 10 # 引数を変更
y = 0
change_arg(y)
print(y) # 結果:0
ミュータブルな変数の値渡し
値がイミュータブルだと、値が変更されるたびに新しいメモリの位置に保存されるようなことはされず、変数の参照先も変わらない。そのため、以下のように関数内での変更の影響が関数外にも及ぶ。
def change_arg(x):
x.append(1) # 引数を変更
y = []
print(y) # 結果:[]
change_arg(y)
print(y) # 結果:[1]
デフォルト引数は1度だけしか評価されない
特にミュータブルなデフォルト引数には注意が必要。
これにより生じる問題
L=[]
が実行されるのが1度だけなので、その後はLは更新されない。
def f(a, L=[]):
L.append(a)
return L
print(f(1)) # [1]
print(f(2)) # [1, 2]
print(f(3)) # [1, 2, 3]
対策
def f(a, L=None):
if L is None:
L = []
L.append(a)
return L
引数の渡し方の指定
-
/
の前は位置でしか引数を渡せない -
*
の後はキーワードでしか引数を渡せない -
/
と*
の間であれば、位置で渡してもキーワードで渡してもいい
def combined_example(pos_only, /, standard, *, kwd_only):
print(pos_only, standard, kwd_only)
# 呼び出し
# "standard"はstandard="standard"でも可
combined_example("pos", "standard", kwd_only="keyword")
任意個数の引数
*
を付けると任意個数の引数を受け取れる
def concat(*args, sep="/"):
return sep.join(args)
concat("earth", "mars", "venus", sep=".") # 出力:earth.mars.venus
辞書型の引数リストのアンパック
**
で辞書型をアンパックできる。
def parrot(voltage, state='a stiff', action='voom'):
print("-- This parrot wouldn't", action, end=' ')
print("if you put", voltage, "volts through it.", end=' ')
print("E's", state, "!")
# 関数の引数と同じ名前をキーに持つ辞書型を用意
d = {"voltage": "four million", "state": "bleedin' demised", "action": "VOOM"}
parrot(**d) # アンパック
集合型
a = {x for x in 'abracadabra' if x not in 'abc'} # 'abc'がset()
print(a) # 出力:{'r', 'd'}
__init__.py
ディレクトリをパッケージとして扱わせるためには、__init__.pyが必要。__init__.pyは空でもいいが、パッケージに初期化コードを実行したりしてもいい。
from package import *
の挙動の決定
packageディレクトリ下の__init__.pyに
__all__ = ["script_1", "script_2"]
と書くことで、from package import *
としたときは、packageという名前のパッケージからscript_1.pyとscript_2.pyを読み込む、という設定ができる。
親パッケージの親パッケージからへの参照
from .. import formats
from ..filters import equalizer
自作イテレータ
__next__というメソッドを持つオブジェクトを返す__iter__というメソッドをクラスに実装すればよい。
class Reverse:
"""Iterator for looping over a sequence backwards."""
def __init__(self, data):
self.data = data
self.index = len(data)
def __iter__(self):
return self
def __next__(self):
if self.index == 0:
raise StopIteration
self.index = self.index - 1
return self.data[self.index]
ジェネレータ
データを返すときはyieldを使う。next()が呼ばれるたびに、ジェネレータは中断した処理を再開する。
def reverse(data):
for index in range(len(data)-1, -1, -1):
yield data[index]