- 自分の記憶が曖昧なところを中心に作成しています
- 章番号は公式テキスト「Python実践レシピ」と合わせてあります
- 本記事内のコードは覚えるためのものであり、動作しません
1.Pythonの環境(出題数1:2.5%)
1.1 pip
-
pip install --upgrade package
最新バージョンに更新する -
pip list -o
またはpip list --outdated
最新ではないパッケージを表示する -
pip uninstall package
アンインストール -
pip install -r requirements.txt -c constraints.txt
- constraints.txtにバージョン制限をかけたいパッケージを記述する
- 開発プロジェクトで作るモジュールが複数あるケースで、バージョン制限をかけたいときに使う
1.2 venv
python -m venv --upgrade-deps
すると仮想環境用のpipが最新になる
仮想環境の有効化
OS | シェル | コマンド |
---|---|---|
Unix | bash\zsh | source .venv/bin/activate |
Unix | fish | source .venv/bin/activate.fish |
Unix | csh/tcsh | source .venv/bin/activate.csh |
Windows | コマンドプロンプト | .venv\Sctipts\activate.bat |
Windows/Unix | PowerShell | .vnev\bin or Scripts\Activate.ps1 |
2.コーディング規約(出題数2:5%)
2.1 PEP8
- インデントはスペース4文字
- 1行は79文字
- 関数、クラス間は2行空ける
- クラスメソッドは1行空ける
- importは
標準ライブラリ
→サードパーティ
→ローカルライブラリ
の順番 - importの後は2行空ける
- コメントは"#"の後にスペース1文字空ける
- docstringが複数行の場合、2行目は空ける
def func():
"""関数の短い説明を書く
1行空けて、
説明を書く
"""
命名規則
タイプ | ルール | 例 |
---|---|---|
パッケージ/モジュール名 | lowercase |
system datetime
|
クラス名 | CamelCase | TestCase |
変数/関数名 | lowercase_with_underscores | is_connected |
定数 | UPPEERCASE_WITH_UNDERSCORES | MAX_OVERFLOW |
例外 | CamelCase+Error | ClientApiError |
2.2 Flake8
静的コード解析
タイプ | 内容 |
---|---|
E または W | PEP8由来のエラー |
F | 不具合の元になりそうな書き方 |
C | 複雑さのエラー |
- 特定のエラーを無視する
flake8 --ignore=E203,W503
- 特定の行だけエラーを無視する
aaa = lambda x: 2 + x # noqa: E731
- 特定のファイル等を無視する
flake8 --exclude=.venv,build
- 1行の最大文字数を変更する
flake8 --max-line-length=88
- 複雑度チェック
flake8 --max-complexity=10
- 設定ファイルは
setup.cfg
tox.ini
.flake8
のいずれか
2.3 Black
妥協のないコードフォーマッターで自動整形ツール
import順序の修正はisort
を別途使う
オプション | 説明 |
---|---|
-l, --line-length | 1行の最大文字数(デフォルト88文字) |
--check | チェック結果(全行)を出力、修正はしない |
--diff | チェック結果(差分)を出力、修正はしない |
--include | 対象ファイル/ディレクトリ指定 |
--exclude | 除外ファイル/ディレクトリ指定 |
- 設定ファイルは
pyproject.toml
~/.config/black
(Unix)~/.black
(Windows)
ファイル種別 | Black | Flake8 | mypy |
---|---|---|---|
pyproject.toml | 〇 | × | 〇 |
setup.cfg | × | 〇 | 〇 |
その他 |
.config/black 、.black
|
.flake8 、tox.ini
|
mypy.ini 、.mypy.ini
|
3.Pythonの言語仕様(出題数7:17.5%)
3.1 例外
BaseException
├─ SystemExit
├─ KeyboardInterrupt
├─ GeneratorExit
└─ Exception
├─ ArithmeticError 算術エラーの基底クラス
│ ├─ OverflowError
│ └─ ZeroDivisionError
│
├─ AttributeError 存在しない属性を参照
├─ LookupError
│ ├─ IndexError list
│ └─ KeyError dict
│
├─ OSError
│ └─ FileNotFoundError
│
├─ SyntaxError
│ └─ IndentationError 空白の不備
│ └─ TabError
│
├─ TypeError 演算・操作で不適切な型
├─ NameError 未定義の変数を使ったとき
└─ ValueError 演算・操作で不適切な値
3.2 with文
ContextManager
class sample:
def __init__(self, file_path):
self.file_path = file_path
def __enter__(self):
"""ファイルを開く"""
self.file_obj = open(self.file_path)
return self.file_obj
def __exit__(self,
type, # 例外の型
value, # 例外のメッセージ
traceback # スタックトレース
):
"""ファイル閉じる"""
print(type(e))
print(e)
print(スタックトレースは長いので省略)
self.file_obj.close()
with sample('./sample.txt') as f:
print(f.read())
raise ValueError('my context managaer error')
ContextManagerデコレーター
import contextlib
@contextlib.contextmanager
def sample(file_path):
file_obj = open(file_path)
try:
# __enter__
yield file_obj
except Exception as e:
# __exit__例外
print(type(e))
print(e)
print(スタックトレースは長いので省略)
raise
finally:
# __exit__通常
file_obj.close()
with sample('./sample.txt') as f:
print(f.read())
raise ValueError('my context managaer error')
その他のcontextlib機能
import contextlib
import os
with contextlib.suppress(FileNotFoundError):
os.remove('dummy.txt') # ファイルがなくても例外にならない
with open('sample.log', 'w') as f:
with contextlib.redirect_stdout(f):
print('log_write') # ファイルに出力される
3.3 関数の引数
位置引数
func(param1, param2, param3)
位置専用引数
def func(x, y, /):
pass
func(1, 2) # OK
func(x=1, y=2) # NG
キーワード引数
func(param1=1, param2=2, param3=3)
- この呼び出しはエラー
func(param3=3, 1, 2)
デフォルト付き引数
- これはエラー
def func(param1=1, param2, param=3):
可変長引数
def func(*args):
for arg in args
print(arg)
func(1, 2, 3, 4, 5)
# listとtupleは*を付けて呼び出す
nums = [4, 5, 6, 7]
func(*nums)
可変長キーワード引数
def func(**kwargs):
for k, v in kwargs.items() # dict型になる
print(f'{k} {v}')
func('name':'john', 'age':30)
キーワード専用引数
def func(param1, *, param2):
pass
func(1, param2=2) # *以降はキーワード指定が必須
3.4 アンパック
nums = (1, 2, 3)
a, b, c = nums
# a=1, b=2, c=3
nums = (1, 2, (3, 4, 5))
a, b, c = nums
# a=1, b=2, c=(3, 4, 5)
a, b, (c, d, e) = nums
# a=1, b=2, c=3, d=4, e=5
nums = (1, 2, 3, 4)
a, b, *c = nums
# a=1, b=2, c=[3, 4]まとめて入る
a, *b, *c = nums
# 2つ以上はエラーになる
3.6 ジェネレーター
return
ではなくyield
にするとジェネレーターになる
def func(values):
for i in values:
yield i * 2
ret = func([0, 1, 2, 3])
print(type(ret)) # <class 'generator'>
# 長さを取得
len(ret) # NG
len(list(ret)) # リストに変換する必要がある
next(ret) # 0
next(ret) # 1
next(ret) # 2
next(ret) # 3
next(ret) # StopIteration例外
3.7 デコレーター
@my_decorator1
@my_decorator2
def func():
psss
# 以下と同じ
func = my_decorator1(my_decorator2(func))
__name__
や__doc__
(docstring)がデコレーターに上書きされてしまうが、functools.wraps
を使うと元関数の情報を返すことができる。
from functools import wraps
def my_decorator(func):
@wraps(func):
def wrap_func(a):
"""wrap_funcのdocstringです"""
func(a)
return wrap_func
@my_decorator
def func(name):
"""funcのdocstringです"""
print(name)
print(func.__doc__) " funcのdocstringです
4.Pythonのクラス(出題数3:7.5%)
4.1 class構文
- Pythonはマルチパラダイムプログラミング言語
- オブジェクト指向、手続き型、関数型いずれも書ける
4.2 特殊メソッド
メソッド | 説明 |
---|---|
__init__ |
コンストラクタ |
__call__ |
メソッドのように( )で呼び出せるようにする |
__repr__ |
print出力を変更できる |
__str__ |
文字列型への変換処理 |
__len__ |
len()関数の処理 |
__lt__ |
<演算子 (lはlower→小なり) |
__le__ |
<=演算子 (eはイコール) |
__eq__ |
==演算子 |
__ne__ |
!=演算子 |
__gt__ |
>演算子 (gはgrator→大なり) |
__ge__ |
>=演算子 |
__add__ |
+演算子 |
__sub__ |
-演算子 |
__mul__ |
*演算子 |
__truediv__ |
/ 演算子 |
4.3 継承
-
super()
関数で親クラスのメソッドを呼び出せる - 多重継承は
class Custom(Base1, Base2)
- 継承順位をMROという仕組みで、C3アルゴリズムで探索する
4.4 dataclass
-
__init__
の定義を省略できる
@dataclass(frozen=true) # データ変更するとFrozenInstanceErrorになる
class User:
name: str
age: int
active: bool = True # デフォルト値は最後に定義する
user = User('Taro', 30)
dic = asdict(user) # 辞書型に変換できる
tup = astuple(user) # タプルに変換できる
4.5 オブジェクト関連関数
関数名 | 説明 |
---|---|
id(object) |
objectの識別値を返す |
type(object) |
型オブジェクトを返す |
isinstance(object, class_info) |
objectがclass_infoインスタンスであるか判定する |
issubclass(class, class_info) |
classがclass_infoのサブクラスであるか判定する |
help(object) |
docstringを返す |
dir(object) |
objectが持つ属性・メソッドのリストを返す |
isinstance(True, bool) # True
isinstance(True, int) # bool値はintを継承しているのでTrue
デコレーター
@classmethod
メソッド内でクラス自身を参照できるようにするデコレーター
@staticmethod
インスタンス化せずにメソッドを使えるようにするデコレーター
@property
class Hoge:
@property
def length(self):
cnt = ・・・ # データ数を数える処理
return cnt
# ()なしで呼び出せる
hoge_length = hoge.length
5.型ヒント(出題数2:5%)
5.2 mypyは出題無し
5.1 typingモジュールを利用した型ヒント
Union
from typing import Union
def func(number: Union[int, str]):
pass
func(100)
func("100")
Optional
from typing import Optional
price: Optional[int] # 数値またはNone
Python3.10からtyping
をimport
せずにUnion
とOptional
が書ける
def func(number: int | str) # Union
pass
price: int | None # Optional
Literal
特定の値のみ許可する
from typing import Literal
FILETYPE = Literal["csv", "json"]
def func(path: str, file_type: FILETYPE):
pass
func("sample.csv", "csv") # OK
func("sample.html", "html") # NG
TypeVar
C++のTemplateのイメージ
from typing import TypeVar
T = TypeVar("T", int, float)
def add_one_to_t_arg(arg: T) -> T:
return arg + 1
TypedDict
dict型よりも厳密に型を付ける
from typing import TypedDict
class BookDict(TypedDict):
name: str
author: str
price: int
book: BookDict = {
"name": "Python実践レシピ",
"author": "鈴木たかのり",
"price": 2700
}
6.テキストの処理(出題数4:10%)
6.1 str, string
チェックメソッド
メソッド | 解説 |
---|---|
string.istitle() |
先頭大文字、残り小文字ならTrue |
string.isdecimal() |
数値(全角、半角)ならTrue |
string.isdigit() |
True:①、False:ローマ数字/漢数字 |
string.isnumeric() |
① ローマ数字/漢数字もTrue |
変換メソッド
メソッド | 解説 |
---|---|
string.capitalize() |
先頭を大文字に、それ以外を小文字にする |
string.title() |
単語ごとに大文字1文字+小文字にする |
string.zfill(width) |
長さがwidthになるよう左に0を埋める |
string.removeprefix(prefix, /) |
先頭からprefixを削除する ※位置専用引数なので string.removeprefix(prefix='hoge') では呼び出せない |
string.removesuffix(suffix, /) |
末尾からsuffixを削除する |
その他メソッド
メソッド | 解説 |
---|---|
find(sub, start[, end])->int |
subが出現する位置を返す。なければ-1。 ※search( )は正規表現のreモジュールなので引っ掛け注意 |
encode(encording='utf-8', errors='strict', replace, ignore)->str |
errors='strict' (デフォルト)は変換できなければUnicodeEncodeError例外replace :変換できない文字は?になるignore :無視する(その文字は変換されない) |
文字列定数
定数 | 内容 |
---|---|
string.ascii_lowercase |
英小文字 |
string.ascii_uppercase |
英大文字 |
string.ascii_letters |
英大文字+小文字 |
string.digits |
0~9 |
string.hexdigits |
0~9+ABCDEF |
string.octdigits |
0~7 |
string.punctuation |
記号 |
string.whitespace |
空白 \t \n \t \r \x0b \x0c |
string.printable |
ascii_letters + digits + punctuation + whitespace |
import string
print('a' in string.ascii_lowercase)
6.2 f-string
書式 | 解説 |
---|---|
{aaa:<10} |
左寄せ10文字埋め |
{aaa:^10} |
中央ぞろえ10文字埋め |
{aaa:>10} |
右寄せ10文字埋め |
{aaa:b} |
2進数 |
{aaa:X} |
16進数(大文字) 小文字もある |
{aaa:%} |
百分率+% |
{aaa:,} |
3桁カンマ区切り |
{aaa:6.2f} |
全体が6桁で、小数点は2桁 |
{aaa:%Y-%m-%d %H:%M:%S} |
YYYY-mm-dd HH:MM:SS |
print(f'{name=}')
変数名+値の形式で出力される(デバッグで使う)
6.3 正規表現(reモジュール)
search関数
import re
mt = re.search('a.c', 'abcde')
print(mt)
# re.Match object: span(0, 3), match='abc'
# マッチしなければNoneが返る
フラグ | 解説 |
---|---|
re.A |
ASCII文字 |
re.I |
大文字・小文字を区別しない |
re.M |
複数行の先頭と末尾 |
re.S |
.が改行文字含めてマッチする |
re.search('a.c', 'a\nc', re.S) # .と\nがマッチする
正規表現の特殊文字一覧
特殊文字 | 説明 |
---|---|
\d |
数字 |
\D |
数字以外 |
\s |
空白文字 |
\S |
空白文字以外 |
\w |
任意の英数字 |
\W |
英数字以外 |
. |
任意の1文字 |
^ |
先頭 |
$ |
末尾 |
* |
0文字以上の繰り返し |
+ |
1文字以上の繰り返し |
? |
0回か1回の繰り返し |
{m} |
m回の繰り返し |
{m, n} |
m回以上、n回以下の繰り返し |
[abc] |
指定した いずれかの文字(この場合a or b or c ) |
[^abc] |
指定した いずれかの文字以外(この場合、a/b/c以外) |
(x|y) |
x かy の選択 |
compile(正規表現オブジェクト)
search()
の第一引数をオブジェクト化するイメージ。
import re
pattern = re.compile('a.c')
mt = pattern.seach('abcde')
print(mt)
# Match object: span(0, 3), match='abc'
compileオブジェクトのメソッド
メソッド名 | 解説 | 戻り値 |
---|---|---|
search(string) |
stringが正規表現にマッチするか返す | Matchオブジェクト or None |
match(string) |
先頭から正規表現にマッチするか返す | Matchオブジェクト or None |
fullmatch(string) |
string全体が正規表現にマッチするか返す | Matchオブジェクト or None |
split(string) |
正規表現にマッチした文字列で分割する | list[str] |
sub(replace, string) |
マッチした文字列をreplaceに置き換える | str |
findall(string) |
マッチした文字列をリストで返す | list[str] |
finditer(string) |
マッチした文字列をIteratorで返す | MatchのIterator |
import re
regex1 = re.compile('[-+()]')
print(regex1.split('+81(80)1234-5678')
# ['080', '1234', '5678']
print(regex1.sub('_', '+81(80)1234-5678')
# _81_80_1234_5678
Matchオブジェクト
import re
mt = re.match(r'(\d+)-(\d+)-(\d+)', '080-1234-5678')
print(mt.group(0)) # 全体
# '080-1234-5678'
print(mt.group(1)) # 1番目のグループ
# '080'
print(mt.group[2]) # 2番目のグループ(添え字でもOK)
# '1234'
名前付きサブグループは(?P<name>)
という形式で記述する。
import re
mt = re.match(r'(?P<family_name>\w+) (?P<first_name>\w+)', '鈴木 太郎')
print(mt.group('family_name')
# '鈴木'
print(mt.group['first_name']) # dictみたいにもアクセスできる
# '太郎'
print(mt.groupdict())
# 'family_name': '鈴木', 'first_name': '太郎'
print(mt.expand(r'名字:\g<family_name> 名前:\g<first_name>'))
# '名字:鈴木 名前:太郎'
6.4 unicodedataモジュール
import unicodedata
print(unicodedata.lookup('BEER MUG'))
# '🍺'
print(unicodedata.name('🍣'))
# 'SUSHI'
normalize(正規化)
import unicodedata
print(unicodedata.normalize('NFC', '123abcアイウエオ①②③¹²³')) # そのまま出力
# 123abcアイウエオ①②③¹²³
print(unicodedata.normalize('NFKC', 'アガ')) # 半角カナが全角カナになる
# 'アガ'
print(list(unicodedata.normalize('NFKD', 'アガ'))) # 濁点が分割される
# ['ア', 'カ', '゛']
7.数値の処理(出題数0)
pass
8.日付と時刻の処理(出題数2:5%)
8.4 dateutilは出題なし
8.1 datetimeモジュール
dateオブジェクト
from datetime import date
が必要。
メソッド | 備考 |
---|---|
date.today() |
f'{date.today():%Y年 %m月 %d日}' |
date.weekday() |
月曜日が0~日曜日が6 |
date.isoweekday() |
月曜日が1~日曜日が7 |
date.isoformat() |
YYYY-MM-DD形式で返す |
date.fromisoformat('2024-12-31') |
YYYY-MM-DD文字列からdateオブジェクトを作る |
date.strftime('%Y/%m/%d') |
任意のフォーマットで文字列を返す |
timeオブジェクト
from datetime import time
が必要。
メソッド | 備考 |
---|---|
time.isoformat(timespec='auto') |
'12:59:59.123456'が返る |
time.fromisoformat('12:59:59.123456') |
文字列からオブジェクトを作る |
time.strftime('%H時 %M分 %S秒') |
任意のフォーマットで文字列を返す |
from datetime import time
t1 = time(12, 25, 55, 999999) # 12時25分55秒 123456マイクロ秒
t2 = time.fromisoformat('12:25:55.123456')
time.fold
はサマータイムが終わって時刻が巻き戻った期間の時に「1」にする
datetimeオブジェクト
from datetime import datetime
が必要。
date
とtime
を合わせたもの。
メソッド | 備考 |
---|---|
datetime.now(tz=None) |
タイムゾーンを渡せる |
datetime.utcnow() |
UTC日時を返す |
datetime.strftime('%Y/%m/%d') |
任意のフォーマットの文字列を返す |
datetime.strptime('2021/12/31', '%Y/%m/%d') |
任意のフォーマットからdatetimeオブジェクトを作るstrftime とstrptime は動きが逆なので注意 |
datetime.fromisoformat('2021-12-31') |
ISOフォーマットからdatetimeオブジェクトを作る |
-
date
time
datetime
のfromisoformat()
にパースできない文字列を渡すとValueError
が発生する - タイムゾーン情報を含む:Awareオブジェクト
- タイムゾーン情報を含まない:Nativeオブジェクト
- 比較や差分計算をする場合は種類を合わせる必要がある
- 種類が違うと
TypeError
が発生する
strftime()で使える指定子
指定子 | 備考 |
---|---|
%Y | 4桁の西暦 |
%y | 下2桁の西暦(2002の場合は02) |
%m | 月(0埋め2桁) |
%d | 日(0埋め2桁) %Dは無い |
%H | 時(24時間表記) %hは無い |
%M | 分(0埋め2桁) |
%S | 秒(0埋め2桁) %sは無い |
8.2 timeモジュール
import time
が必要。
1970年1月1日からの経過時間を扱う。
(datetime
モジュールのtime
とは別なので注意)
関数名 | 説明 |
---|---|
time.time() |
1970年1月1日から現在の経過時間(秒)をfloatで返す |
time.gmtime([経過時間(秒)]) |
1970年1月1日から指定秒経過後のUTC時刻(time.struct_time構造体)を返す (UTCは日本時間の9時間前) 引数未指定なら現在時刻が返る |
time.localtime([経過時間(秒)]) |
gmtime( )の日本時間が返る版 |
time.strftime('%Y-%m-%d', time.localtime()) |
経過時間から日付文字列を返す |
time.time()
で簡易的に実行時間を計測できるが、timeit
モジュールを使うほうが正確(?)
time.sleep関数
import time
time.sleep(0.5) # 0.5秒止める(全スレッド)
自スレッドだけ止める場合はimport asyncio
してasyncio.sleep(0.5)
を使う。
8.3 zoneinfoモジュール
from zoneinfo import ZoneInfo
from datetime import datetime
tokyo = ZoneInfo('Asia/Tokyo')
dt = datetime(2021, 12, 31, tzinfo=tokyo)
サードパーティにpytz
がある。
pytz
はfold
(サマータイム終了後の巻き戻り期間中を表す属性)には対応していない。
9.データ型とアルゴリズム(出題数5:12.5%)
9.3 bisect と 9.5 pprint は出題無し
9.1 ソート(sorted, sort, operator)
sorted
: 入力データはそのままで、ソート済みの新しいlistを返す(非破壊的操作)
sort
: 入力データ自体をソートする(破壊的操作)
print(sorted(['B', 'D', 'a', 'c'], key=str.lower)) # 小文字に変換して比較する
# ['a', 'B', 'c', 'D']
operatorモジュール
from operator import itemgetter
# 複数キーの指定
data = [(2, 'c', 100), (1, 'b', 200), (2, 'a', 300)]
print(sorted(data, key=itemgetter(0, 1))) # tupleの0番目、1番目をキーにソートする
# [(1, 'b', 200), (2, 'a', 300), (2, 'c', 100)]
# 辞書のValueでソート
user_dict = [{'name': 'terada', 'age': 35},
{'name': 'suzuki', 'age': 25},
{'name': 'sugita', 'age': 30}]
print(sorted(user_dict, key=itemgetter('age'))
# suzuki(25) → sugita(30) → terada(35)の順番になる
date.month
、date.day
など .
で属性にアクセスする物をソートする場合はattrgetter
を使う。
from operator import attrgetter
from datetime import date
dates = [date(1989, 1, 4),
date(1970, 11, 28),
date(1984, 3, 4)]
print(sorted(dates, key=attrgetter('month', 'day))
# 1989/1/4 → 1984/3/4 → 1970/11/28の順になる
9.2 collectionsモジュール
Counterクラス
件数をカウントするクラス。
from collections import Counter
cnt = Counter('aaabbc')
print(cnt)
# {'a':3, 'b':2, 'c':1}
from collections import Counter
cnt = Counter('cbbaaa')
print(list(cnt.elements())) # カウントキーのイテレーター
# ['c', 'b', 'b', 'a', 'a', 'a']
from collections import Counter
cnt = Counter('cbbaaa')
print(cnt.most_common(2)) # 大きい順に2つ取り出す
# [('a', 3), ('b', 2)]
from collections import Counter
# update()でCounter同士を足す
cnt = Counter('cbbaaa')
cnt2 = Counter('cba')
cnt.update(cnt2)
print(cnt)
# {'c':2, 'b':3, 'a':4 }
from collections import Counter
#subtract()でCounter同士を引く
cnt = Counter('cbbaaa')
cnt2 = Counter('cba')
cnt.subtract(cnt2)
print(cnt)
# {'b':1, 'a':2}
defaultdictクラス
存在しないキーにアクセスしてもKeyError
にならない辞書。
from collections import defaultdict
def default_func():
return 'default_value'
d = defaultdict(default_func)
print(d['apple'])
# KeyErrorにならず'default_value'になる
d2 = defaultdict(int)
print(d2['apple'])
# intを指定すると0になる
OrderdDictクラス
Python3.7からdict型が挿入順を保持できるようになったので、最近は使われない。
from collections import OrderdDict
d = OrderdDict(one=1, two=2, three=3)
# 順番を移動する
d.move_to_end('two')
print(d)
# [('one', 1), ('three', 3), ('two', 2)]
# last=Falseにすると先頭に移動する
d.move_to_end('two', last=False)
print(d)
# [('two', 2), ('one', 1), ('three', 3)]
# 末尾を取り出す
print(d.popitem())
# ('three', 3)
# 先頭を取り出す
print(d.popitem(last=False))
# ('two', 2)
namedtuple
名前付きタプル
from collections import namedtuple
Pet = namedtuple(typename='Pet' # 型の名前
field_names='animal, name, age')
pet = Pet('dog', 'ポチ', 3)
print(pet)
# Pet(animal='dog', name='ポチ', age=3)
9.4 enumモジュール
enum.auto()
を使うと自動で連番が振られる
import enum
clann Nengo(enum.Enum):
SHOWA = enum.auto()
HEISEI = enum.auto()
REIWA = enum.auto()
uniqueデコレーター
import enum
@enum.unique
class Spam(enum.Enum):
HAM = 1
BACON = 1
# 実行するとValueError例外になる
定数の参照
import enum
class Nengo(enum.Enum):
SHOWA = enum.auto()
HEISEI = enum.auto()
REIWA = enum.auto()
print(Nengo.REIWA) # <Nengo.REIWA: 3>
print(Nengo['REIWA']) # <Nengo.REIWA: 3>
print(Nengo(3)) # <Nengo.REIWA: 3>
nengo = Nengo.REIWA
print(nengo.name) # 'REIWA'
print(nengo.value) # 3
Enumの比較
import enum
class Spam(enum.Enum):
HAM = 1
EGG = 2
BACON = 2
print(isinstance(Spam.HAM, Spam)
# True (HAMはSpam型インスタンスだから)
print(Spam.EGG == Spam.BACON)
# True (別名でも同じclassで値が同じだから)
import enum
class Spam(enum.Enum):
HAM = 1
EGG = 2
BACON = 2
class Spam2(enum.Enum):
HAM = 1
EGG = 2
BACON = 2
print(Spam.HAM == Spam2.HAM)
# False (別のクラスだから)
print(Spam.HAM == 1)
# False (マジックナンバーとの比較しても一致しない)
引数の型ヒント以外を入力してもエラーにならないので、mypyなどの静的解析をすると良い
import enum
class Spam(enum.Enum):
HAM = 1
EGG = 2
def func(spam: Spam)
print(spam)
func('BECON') # エラーにならない
# 'BECON'
func(Spam['BECON']) # これはKeyErrorになる
9.6 itertoolsモジュール
chain関数
複数のイテラブルオブジェクトを連結する
import itertools
l = ['A', 'B']
s = 'ab'
t = (1, 2)
itr = itertools.chain(l, s, t)
for m in itr:
print(m)
# 'A' → 'B' → 'a' → 'b' → 1 → 2
groupby関数
import itertools
for value, group in itertools.groupby('aaabbcdddaabb'):
print(f'{value}: {list(group)}')
# a: ['a', 'a', 'a']
# b: ['b', 'b']
# c: ['c']
# d: ['d', 'd', 'd']
# a: ['a', 'a']
# b: ['b', 'b']
import itertools
def is_odd(num):
if num % 2 == 1:
return '奇数'
else:
return '偶数'
numbers = [10, 20, 31, 11, 3, 4]
for value, group in itertools.groupby(nembers, key=is_odd):
print(f'{value}: {list(group)}')
# '偶数': [10, 20]
# '奇数': [31, 11, 3]
# '偶数': [4]
islice関数
指定したイテラブルオブジェクトから、指定した範囲のイテレーターを作成する。
import itertools
l = [0, 1, 2, 3, 4]
l2 = itertools.islice(l, 3) # 先頭から3つを指定
print(l2)
# [0, 1, 2]
islice(iterable, start, stop, step=1)
の指定もできる。
iterableの長さが不明な場合、start, stop, stepにマイナス値を指定するとValueError
になる
zip関数
組み込み関数のため、itertools
のimport
は不要。
短いほうに合わせる。
l1 = [0, 1, 2, 3]
l2 = ['a', 'b', 'c']
for m in zip(l1, l2):
print(m)
# (0, 'a')
# (1, 'b')
# (2, 'c')
python3.10でstrict=True
引数が追加されて、長さが異なるとValueError
になる。
zip_longest関数
itertools
のimport
が必要。
import itertools
l1 = [0, 1, 2, 3]
l2 = ['a', 'b', 'c']
for m in itertools.zip_longest(l1, l2, fillvalue='-'):
print(m)
# (0, 'a')
# (1, 'b')
# (2, 'c')
# (3, '-') # fillvalue未指定の場合はNoneになる
組み合わせイテレーターを取得する関数
関数 | 説明 | 結果例 |
---|---|---|
itertools.product(*itr, repeat=1) |
各itrの全要素の組み合わせ | AA, AB, AC, BA, BB, BC, CA, CB, CC |
itertools.permutations(itr, r=None) |
順列 | AB, AC, BA, BC, CA, CB (AA, BB, CCがない) |
itertools.combinations(itr, r) |
重複なしの組み合わせ | AB, AC, BC |
itertools.combinations_with_replacement(itr, r) |
重複ありの組み合わせ | AA, AB, AC, BB, BC, CC |
9.7 copyモジュール
copy関数
1層までは新しいオブジェクトになるが、多次元リストなど2層以上は参照コピーになる。
import copy
values = [1, 2, 3, 4]
values2 = copy.copy(values)
deepcopy関数
多次元リストなど2層以上も新しいオブジェクトになる。
10.汎用OS・ランタイムサービス(出題数2:5%)
10.1 osモジュール
ファイルとディレクトリ操作
後者は中間のディレクトリも作成する。
-
mkdir
とmkdirs
-
rename
とrenames
中間ディレクトリのパーミッションはos.umask(8進数)
で設定できる。
ファイルパスの定数
定数名 | 説明 |
---|---|
os.curdir |
カレントディレクトリ |
os.pardir |
親ディレクトリ |
os.sep |
ディレクトリ区切り文字 |
os.extsep |
拡張子区切り文字 |
os.linesep |
行の終端 |
システム情報の関数
利用できるのはUnixのみ
関数名 | 説明 |
---|---|
os.confstr(str) |
システム設定値を文字列で返す。 渡す文字列は os.confstr_names で取得できる。 |
os.sysconf(str) |
システム設定値を数値で返す。 渡す文字列は os.sysconf_names で取得できる。 |
os.getloadavg() |
ロードアベレージを返す (過去1分間、5分間、15分間) |
os.cpu_count() |
CPU数を取得する。取得できなければNoneを返す。 |
multiprocessing.cpu_count()
は取得できないとNotImplementedError
が発生する。
psutil
モジュール(サードパーティ)もある。
関数名 | 説明 |
---|---|
psutil.cpu_times() |
CPU時間を求める |
psutil.cpu_times_percent(interval=1, percpu=False) |
特定のCPU時間ごとの使用率 |
psutil.virtual_memory() |
メモリの使用率 |
psutil.disk_partitions() |
ディスクパーティション情報 |
psutil.net_io_counters() |
ネットワークI/O |
ランダムな文字列を生成
print(os.urandom(10))
# b'<10バイト分のデータ>'
randam
モジュールは簡易的なものなので、セキュリティを考慮する必要がある場合はos.urandom()
もしくはsecrets
モジュールを使う。
10.2 ioモジュール
ストリーム | 説明 |
---|---|
io.StringIO クラス |
メモリー上でテキストファイルのように扱う |
io.BytesIO クラス |
メモリー上でバイナリファイルのように扱う |
- ストリームを
close()
した後に書き込み等を行うとValueError
が発生する -
io.BytesIO
のgetbuffer()
は値変更すると元データも変更される-
getvalue()
は値変更できない
-
- ストリームを他関数に渡すときは
seek(0)
で先頭に戻さないと意図する動きにならないかもしれない
10.3 sysモジュール
sys.path
sys.path
はパッケージ・モジュールの探索するときに参照される。
以下の順番でパスが入っている。
- pyファイルと同じディレクトリ、カレントディレクトリ
PYTHONPATH
- Pythonのインストール先
sys.breakpointhook関数
import sys
def func():
print('hello')
sys.breakpointhook = func
print('start')
breakpoint() # ここでfunc()が呼び出される
print('end')
10.4 argparseモジュール
import argparse
parser = argparse.ArgumentParser(
prog=sys.argv[0], # プログラム名
usage='利用方法',
description='ヘルプ文の前に表示される説明',
epilog='ヘルプ文の後に表示される説明',
parents=[ArgumentParserオブジェクトのリスト],
formatter_class=argparse.HelpFormatter, #フォーマットをカスタマイズするクラス
prefix_chars='-', # 引数の先頭文字 ハイフン以外に変更できる
fromfile_prefix_chars=None, # 引数にファイルを指定する際のプレフィックス @file.txtとか
argument_default=None, # パーサー全体の引数のデフォルト値
conflict_handler='error', # 複数オプション指定された時の挙動
add_help=True, # -hオプションを追加するかどうか
allow_abbrev=True, # 長いオプションを先頭の1文字に短縮できるようにするかどうか
exit_on_error=True # Trueはプロセス終了、Falseはargparse.ArgumentError例外
)
# required=Trueにすると-s/--stringの指定が必須になる
parser.add_argument('-s', '--string', type=str, help='data of string', required=True)
parser.add_argument('--input', type=argparse.FileType('r'))
args = parser.parse_args() # 実行時に指定されたオプションをパースする
print(args.string) # add_argmentで追加したものが属性でアクセスできる
input_file_data = args.input.read() # ファイルが読み込める
Click
というサードパーティパッケージは、デコレーターで同じことができる。
import click
@click.command()
@click.option('-s', '--string', help='data of string', requred=True)
print(string)
11.ファイルとディレクトリへのアクセス(出題数2:5%)
11.1 pathlibモジュール
class PurePath()
"""純粋パス 基底クラス"""
drive: str
root: str
def is_absolute(): # 絶対パスか
def is_relative_to(*other): # otherに対して相対パスか
class PurePosixPath(PurePath) # Unix向け純粋パス
class PureWindowsPath(PurePath) # Windows向け純粋パス
class Path(PurePath)
"""具象パス 基底クラス"""
def cwd()->str: # 現在のディレクトリを返す
def stat()->os.stat_result: # ファイルのプロパティ情報を返す
def rmdir(): # ディレクトリが空であること
class PosixPath(PurePosixPath, Path) # Unix向け具象パス
class Windowspath(PureWindowsPath, Path) # Windows向け具象パス
純粋パスは実際にファイルがなくても扱える。
Path
はpath-like
オブジェクトの一種。
from pathlib import Path
p = Path('sample_dir')
p.mkdir()
p.mkdir() # FileExistsError例外になる
p.mkdir(exist_ok=True) # 例外にならないので、いちいち存在チェックしなくてよくなる
- ファイル削除は
Path.unlink(missing_ok=True)
にすると、存在しなくても例外にならない
11.2 tempfileモジュール
オブジェクト | 説明 | 出力先 |
---|---|---|
tempfile.TemporaryFile() |
ファイル名のない一時ファイルを作成exist() で存在確認できない |
ファイルシステム |
tempfile.NamedTemporaryFile() |
ファイル名がある一時ファイルを作成 | ファイルシステム |
tempfile.SpooledTemporaryFile() |
一定サイズまでメモリーで、超えるとディスクに書き出されるexist() で存在確認できない |
メモリ→ファイルシステム |
tempfile.TemporaryDirectory() |
一時ディレクトリを作成 | ファイルシステム |
11.3 shutilモジュール
高水準なファイル操作
ファイルコピー
関数 | 説明 |
---|---|
shutil.copyfile(src, dst) |
srcファイル dstファイルにコピー ※dstにディレクトリは指定不可 |
shutil.copy(src, dst) |
ファイルのパーミッションもコピー |
shutil.copy2(src, dst) |
すべてのメタデータ(変更時刻等)もコピー |
shutil.copymode(src, dst) |
パーミッションをコピー |
shutil.copystat(src, dst) |
メタデータをコピー |
コピー元・先がシンボリックリンクで同じものを指している場合は、SameFileError
例外が発生する。
再帰的に操作する
関数 | 説明 |
---|---|
shutil.copytree(src, dst, ignore) |
srcディレクトリ以下を再帰的にコピーする |
shutil.move(src, dst) |
srcディレクトリ以下を再帰的に移動する |
shutil.rmtree(path, ignore_errors=False, onerror=None) |
pathで指定したディレクトリ以下を削除する |
import shutil
ignore_pattern = shutil.ignore_patterns('*.pyc', '*.swp')
shutil.copytree('from', 'to', ignore=ignore_pattern) # *.pycと*.swpはコピーされない
12. データ圧縮、アーカイブと永続化(出題数0)
pass
13.特定のデータフォーマットを扱う(出題数2:5%)
以下は出題なし
- 13.3 configparser
- 13.4 PyYAML
- 13.5 openpyxl
- 13.6 Pillow
13.1 CSV
csv.reader() / csv.writer()
import csv
# 読み込み
with open('sample.csv', 'r', encoding='utf-8') as f:
reader = csv.reader(f)
for row in reader:
print(row)
# 書き込み
with open('sample2.csv', 'w', encoding='utf-8') as f:
writer = csv.writer(f,
delimiter='\t', # 区切り文字
quotechar='#', # 引用符
skipinitialspace=True, # 区切り文字直後のスペースを削除
lineterminator='\r\n' # 改行文字)
writer.writerow(row) # 1行書き込み
writer.writerows(rows) # 複数行書き込み
csv.DictReader() / csv.DictWriter()
import csv
dict_data = [
{'都道府県': '東京都', '人口密度': 6335},
{'都道府県': '神奈川', '人口密度': 3807},
{'都道府県': '千葉', '人口密度': 1207}
]
# 書き込み
with open('result.csv', newline='', mode='w', encoding='utf-8') as f:
filed_names = ['都道府県', '人口密度']
writer = csv.DictWriter(f, filednames=filed_names)
writer.writeheader()
writer.writerows(dict_data)
# 読み込み
with open('result.csv', 'r', encoding='utf-8') as f:
# csv.Snifferを使って、先頭1024バイトからCSVの形式を推測して読み込む
data_sample = f.read(1024)
f.seek(0)
print(csv.Sniffer().has_header(data_sample)) # ヘッダ行があればTrue
dialect = csv.Sniffer().sniff(data_sample)
for row in csv.DictReader(f, dialect):
print(row)
13.2 jsonモジュール
関数 | 説明 |
---|---|
json.dumps(obj)->str |
obj(dict等)をJSON文字列にする JSONにできないデータ型があると TypeError 例外になる |
json.dump(obj, file) |
obj(dict等)をfile保存する |
json.loads(json_string)->obj |
JSON文字列からobj(dict等)に変換する |
json.load(file)->obj |
ファイルからobj(dict等)に変換する |
"s"が付けば"string(文字列)"と覚える。
14.インターネット上のデータを扱う(出題数2:5%)
以下は出題なし
- 14.3 Requests
- 14.5 email
14.1 urllib.parseモジュール
parse.urlparse関数
from urllib import parse
parse_result = parse.urlparse('https://www.expample.com/test/;parameter?q=example#hoge')
print(parse_result.scheme) # 'https'
print(parse_result.hostname) # 'www.expample.com'
print(parse_result.path) # '/test'
print(parse_result.query) # ?q=example
print(parse_result.fragment) # #hoge
parse.parse_qs関数
クエリ文字列を解析して、パース結果をdictにする
from urllib import parse
query_dict = parse.parse_qs('q=python&oq=python&sourceid=chrome')
# {'q': ['python'],
# 'oq': ['python'],
# 'sourceid': ['chrome']}
# 同じキーがある場合
query_dict = parse.parse_qs('param=aaa¶m=bbb')
# {'param': ['aaa', 'bbb']}
# keep_blank_values=Trueの場合
query_dict = parse.parse_qs('param1=¶m2=bbb', keep_blank_values=True)
# {'param1': [''], ※デフォルト(False)だとparam1は出力されない
# 'param2': ['bbb']}
parse.parse_qsl関数
パース結果がtupleになる
from urllib import parse
query_tuple = parse.parse_qsl('q=python&oq=python&sourceid=chrome')
# [('q', 'python'), ('oq', 'python'), ('sourceid', 'chrome')]
# 同じキーがある場合、query_qsと違って別々になる
query_tuple = parse.parse_qsl('param=aaa¶m=bbb')
# [('param', 'aaa'), ('param', 'bbb')]
parse.urlencode関数
dict等からクエリ文字列を作成する
from urllib import parse
query_dict = {
'key1': 'hoge',
'key2': ['foo', 'bar']
}
query_str = parse.urlencode(query_dict)
print(query_str)
# 'key1=hoge&key2=foo+bar'
# doseq=Trueの場合
query_str = parse.urlencode(query_dict, doseq=True)
print(query_str)
# 'key1=hoge&key2=foo&key2=bar' ※key2が2つある
# quote_via引数の指定
query_str = parse.urlencode({'key1': ' '})
# 'key1=+' ※デフォルトではスペースは "+" に変換される
query_str = parse.urlencode({'key1': ' '}, quote_via=parse.quote)
# 'key1=%20' ※スペースが%20に変換される
parse.quote関数 / quote_plus関数
特殊文字や2バイト文字をパーセントエンコードする。
関数 | スペース文字 | スラッシュ(/) |
---|---|---|
parse.quote() |
%20 | パーセントエンコードされない |
parse.quote_plus() |
+ | パーセントエンコードされる |
-
_
.
-
~
はデフォルトではパーセントエンコードされない -
parse.quote('_.-~', safe='_.-~')
とすればパーセントエンコードされる
parse.urljoin関数
URLを結合する
from urllib import parse
url_str = parse.urljoin('https://ja.wikipedia.org/wiki/Python', '#ライブラリ')
# 'https://ja.wikipedia.org/wiki/Python#ライブラリ'
# 相対パスの場合
url_str = parse.urljoin('https://ja.wikipedia.org/wiki/Python', '../../wiki/Python')
# 'https://ja.wikipedia.org/wiki/Python'
# URL同士の場合、左側が返される
url_str = parse.urljoin('https://www.python.org', 'https://www.expample.com')
# 'https://www.python.org'
14.2 urllib.requestモジュール
HTTPメソッド |
urlopen() の呼び出し方 |
---|---|
GET |
urlopen(url=<url>) またはurlopen(url=Request(url=<url>))
|
POST |
urlopen(url=<url>, data=string.encode()) またはurlopen(url=Request(url=<url>, data=string.encode()))
|
HEAD | urlopen(url=Request(url=<url>, method='HEAD')) |
PATCH | urlopen(url=Request(url=<url>, method='PATCH')) |
PUT | urlopen(url=Request(url=<url>, method='PUT')) |
DELETE | urlopen(url=Request(url=<url>, method='DELETE')) |
OPTIONS | urlopen(url=Request(url=<url>, method='OPTIONS')) |
from urllib import request
# JPEGをダウンロードしてローカル保存する
response = request.urlopen('https://expample.com/image/jpeg')
file_data = response.read()
with open('result.jpg', 'wb') as f:
f.write(file_data)
print(response.url) # 'https://expample.com/image/jpeg'
print(response.status) # 200
print(response.headers)
# Date: Fri, 07 May 2021 13:55:48 GMT
# Content-Type: image/jpeg
# Content-Length: 1024
# ・・・
from urllib import request
costom_header = {'Accept': 'application/json'}
reqest.urlopen(url=reqest.Request('http://expample.com/', headers=costom_header))
14.3 base64モジュール
Base64エンコード
import base64
s = 'Pythonは簡単に習得でき、それでいて強力な言語の1つです。'
base64.b64encode(s.encode(), altchars=b'@*') # 文字列をバイト列にして渡す
# altcharsの1つ目で'+'を'@'に置き換える
# 2つ目で'/'を'*'に置き換える
# URL向けエンコード処理
base64.urlsafe_b64encode(s.encode())
# デフォルトで'+'は'-'に、
# '/'は'_'に置き換わる
# JSON等に画像を含めたい場合に利用する
with open('./sample.png', 'rb') as png:
b64_img = base64.b64encode(png.read())
data = {'img': b64_img}
json_str = json.dumps(data)
Base64デコード
import base64
s = 'Pythonは簡単に習得でき、それでいて強力な言語の1つです。'
base64_s = base64.b64encode(s.encode())
decode_s = base64.b64decode(base64_s).decode() # バイト列から文字列へのデコードも必要
# urlsafe_b64encode()をデコードする関数もある
decode_s = base64.urlsafe_b64decode(base64_s).decode()
15.HTML/XMLを扱う(出題数0)
pass
16.テスト(出題数3:7.5%)
以下は出題なし
- 16.4 pytest
- 16.5 pydoc
16.1 doctest
- docstringに「>>>実行コード」、次の行に期待結果を記載して、
doctest.testmod()
でテストを動かして検証する- 対話形式でPythonを実行するイメージで書く
- docstringのサンプルコードが実際に動くことを保証する
- CIでdoctestを動かすことで、docstringの更新忘れを検出する
"""
与えられた引数を使って割り算を行う。
>>> div(5, 2)
2.5
"""
def div(a, b):
"""
割り算の答えを少数で返す
>>> [div(i, 2) for i in range(5)]
[0.0, 0.5, 1.0, 1.5, 2.0]
"""
return a / b
def foo()
"""
複数行の書き方
>>> foo() = {'Hermione', 'Harry'}
True
>>> d = sorted(foo()) #集合は順番が保証されないため、ソートしないと失敗する可能性がある
>>> d
['Harry', 'Hermione']
"""
if __name__ == '__main__':
import doctest
doctest.testmod()
$ python doctest_sample.py # 何も表示されなければテスト成功
$ python doctest_sample.py -v # 詳細なログが表示される
Trying:
div(5, 2)
Expecting:
2.5
ok
Trying:
[div(i, 2) for i in range(5)]
Expecting:
[0.0, 0.5, 1.0, 1.5, 2.0]
ok
Trying:
・・・
3 items in 4 items.
4 passed and 0 failed.
Test passed.
def div(a, b):
"""
割り算の答えを少数で返す
第2引数がゼロだった場合、ZeroDivisionError例外がスローされる
>>> div(2, 0)
Traceback (most recent call last):
... # コールスタックが不要な場合は「...」で省略可能
ZeroVisisionError: division by zero
"""
return a / b
doctestをテキストファイルに保存しておき、それを実行することもできる。
import doctest
doctest.testfile('doctest_sample.txt')
16.2 unittest
特徴
- テストの自動化
- 初期設定と終了処理の共有化
- テストの分類
- テスト実行と結果レポートの分離
例外が送出されたかのアサーションはassertRaises(exception)
でチェックする。
テストファイルがおかれているディレクトリに__init__.py
が必要。
subTestで全てのアサートを見る
subTest(msg=None, **params)
import unittest
from expample import add # テスト対象の関数
class AddTest(unittest.TestCase):
def test_add(self):
patterns = [
('a', 'A'),
('b', 'B'),
('c', 'c'), # NG
('d', 'D'),
('e', 'e'), # NG
]
for lower, upper in patterns:
with self.subTest('%s is upper case of %s' % (upper, lower)):
# 'c'で止まらず全てテストされる
self.assertEqual(lower.upper(), upper)
setUp/tearDownの順番
@classmethod setupClass(cls)
(1度だけ)
↓
setUp(self)
(テスト毎)
↓
tearDown(self)
(テスト毎)
↓
@classmethod tearDownClass(cls)
(1度だけ)
コマンドラインから実行
$ python -m unittest test_module1 test_module2 # 2つのモジュールのテスト実行
$ python -m unittest test_module3.TestClass # 特定のテストクラスだけ動かす
$ python -m unittest test_module3.TestClass.test_func # 特定の関数関数だけ動かす
unittestのオプション
オプション | 解説 |
---|---|
-b, --buffer | 標準出力/エラーをテスト実行の間バッファリングする |
-c, --catch | Ctrl+Cされても実行中のテスト終わるまで終了しない 2回目のCtrl+Cされたらすぐ終了する |
-f, --failfast | テスト失敗したらすぐに停止する |
-k | 正規表現で実行するテストを指定する |
--locals | トレースバック内の局所変数を表示する |
discover
サブコマンドで、テストファイルの検出ルールをカスタマイズできる。
オプション | 解説 |
---|---|
-v, --verbose | 詳細な出力を行う |
-s, --start-directory | 検出を開始するディレクトリを指定する (デフォルトはカレントディレクトリ) |
-p, --perttern | テストファイル名のパターンを指定する (デフォルトは test*.py ) |
-t, --top-level-directory | プロジェクトの最上位の検出ディレクトリを指定する |
16.3 unittest.mock
from unittest.mock import MagicMock
from expample import ShoppingAPI # モック対象
api = ShoppingAPI()
# search_item関数の戻り値を差し替える
api.search_item = MagicMock()
api.search_item.return_value = ['モック商品1', 'モック商品2', 'モック商品3']
items = api.search_item(code=12345)
print(items)
# ['モック商品1', 'モック商品2', 'モック商品3']
# 例外を送出するように差し替える
api.search_item.side_effect = Exception('ダミーの例外')
api.search_item(code=12345)
# Traceback (most recent call last):
# Exception: 'ダミーの例外'
patch
import unittest
from unittest.mock import patch
from expample import ShoppingAPI # モック対象
class ExampleTest(unittest.TestCase):
@patch('ShoppingAPI.search_item')
def test_shopping_api(self, APIMock):
api = APIMock()
api.search_item.return_value = ['モック商品1', 'モック商品2', 'モック商品3']
result = api.search_item()
self.assertEqual(result, ['モック商品1', 'モック商品2', 'モック商品3'])
import unittest
from unittest.mock import patch
from expample import ShoppingAPI # モック対象
class ExampleTest(unittest.TestCase):
def test_shopping_api(self):
with patch('ShoppingAPI.search_item') as APIMock:
api = APIMock()
api.search_item.return_value = ['モック商品1', 'モック商品2', 'モック商品3']
result = api.search_item()
self.assertEqual(result, ['モック商品1', 'モック商品2', 'モック商品3'])
patch対象が存在しない場合、ModuleNotFoundError
例外になる。
Mockが呼び出されたかどうかassert
メソッド名 | 解説 |
---|---|
self.assert_called() |
モックが1回以上は呼び出されているか |
self.assert_called_once() |
モックが1回だけ呼び出されているか |
self.assert_called_with(*args, **kwargs) |
モックが指定された引数で1回以上呼び出されたか |
self.assert_called_once_with(*args, **kwargs) |
モックが指定された引数で1回だけ呼び出されたか |
self.assert_not_called() |
モックが1回も呼び出されていないか |
17.デバッグ(出題数2:5%)
17.1 pdb、breakpoint
pdbオプション | 解説 |
---|---|
h | help ヘルプコマンド |
w | where スタックトレースを出力する |
n | next 次の行に進む |
l [s, e] | list 現在の位置のソースコード周囲11行を表示する。s, eで行数を指定できる。 |
c | continue ブレークポイントに到達するまで実行する |
p 変数 | print 変数の値を出力する |
pp 変数 | pprintモジュールを使って変数の値を出力する |
q | quit デバッグを終了する |
breakpointを設定する
import pdb
def func():
a = 1
b = 2
c = a + b
breakpoint() # ここで止まる。pdb.set_trace()でもOK
return c
if __name__ == '__main__':
func()
対話モードでエラーが発生した場合はpdb.pm()
を実行するとデバッグモードになる。
17.2 timeitモジュール
コマンドラインから計測する
$ python -m timeit <実行オプション> '<pythonコード>'
実行オプション | 解説 |
---|---|
-n N , --number=N
|
Pythonコードを実行する回数。未指定の場合、実行時間が0.2秒になる回数 |
-r N , --repeat=N
|
計測の繰り返す回数(デフォルトは5) |
-s <pythonコード> ,--setup=<pythonコード>
|
最初に1度だけ実行する文 (デフォルトはpass ) |
-p , --process
|
実行時間ではなくプロセス時間を計測する |
-u , --unit=<単位>
|
出力する時間の単位を指定するnsec , usec , msec , sec から選択(デフォルトはnsec ) |
-v , --verbose
|
1ループあたりの平均時間ではなく、詳細な計測結果を出力する |
Pythonコードで計測する
import timeit
def func():
"""重たい処理"""
# ガベージコレクション込みで計測する
timeit.timeit('func()', setup='gc.enable()', globals=globals()) # funcはglobalsで名前空間の指定が必要
# オブジェクト化の例
t = timeit.Timer('foo()', setup='gc.enable()', globals=globals())
t.timeit(number=100) # 計測回数100(デフォルト100万回)
t.repeat(repeat=10, number=1000000) # 繰り返し回数10(デフォルト5x100万回)
17.4 loggingモジュール
デフォルトは以下
- メッセージは標準出力に出力される
- 出力フォーマットは「
ログレベル
:ロガー名
:メッセージ
」 - ログレベルはWARNING
ログレベル | 値 | メソッド | 使うタイミング |
---|---|---|---|
CRETICAL | 50 | logging.cretical(msg) |
プログラム全体が動作できないケース |
ERROR | 40 | logging.error(msg) |
プログラムの機能が動作できないケース |
WARNING | 30 |
logging.warning(msg) ※デフォルト値 |
プログラムは動作するが想定外の問題が発生したとき |
INFO | 20 | logging.info(msg) |
プログラムが正常に動作している場合 |
DEBUG | 10 | logging.debug(msg) |
デバッグ用 |
NOTSET | 0 | なし |
メッセージ指定は、f-stringを使わないようにする。
※3.11 + VSCode(Windows)だとf-stringでも動いた
import logging
s = 'Pen'
logging.warning('I have a %s!', s)
# WARNING:root:I have a Pen!
階層構造
logger = logging.getLogger('hoge.fuga.piyo')
とすると、
root
→hoge
→fuga
→piyo
の階層構造ができる。
hoge.fuga
に対して設定をするとpiyo
にも適用される。
logging.basicConfig
import logging
logfmt = '%(asctime)s %(levelname)s %(message)s'
logging.basicConfig(filename='./test.log',
filemode='a',
style='%' or '{' or '$'
format=logfmt,
datefmt='%Y/%m/%d %H:%M:%S.nnn',
level=logging.DEBUG,
stream=ストリーム(filenameと排他),
handlers=実行する関数(filename, streamと排他),
force=True # 他で設定されたものがリセットされる
encoding='utf-8',
errors='?' # 出力できない文字を置き換える(デフォルトはバックスラッシュ?)
フォーマットに使える属性
フォーマット | 解説 |
---|---|
%(asctime)s |
2024-12-31 23:59:59,123 の形式の時刻 |
%(filename)s |
ロギング呼び出しファイル名 |
%(pathname)s |
ロギング呼び出しフルパス |
%(funcName)s |
ロギング呼び出し関数名 |
%(levelname)s |
ログレベル名 |
%(lineno)d |
ロギング呼び出しの行番号 |
%(module)s |
モジュール名(filenameの名前部分) |
%(message)s |
ログメッセージ |
%(name)s |
ロギングの名前(logging.getLogger(name) ) |
%(process)d |
プロセスID |
%(thread)d |
スレッドID |
logging.dictConfig
import logging
from logging.config import dictConfig
config = {
'version': 1, # 1のみサポート
'disable_existing_loggers': False # 既存の設定を無効化しない(デフォルトTrue)
'formatters': { # フォーマットを定義できる
'format1': {
'format': '%(asctime)s %(levelname)s %(message)s'
}
},
'filters': { # フィルターを定義できる
'hoge-filter': {
'name': 'hoge.fuga'
}
},
'handlers': { # ハンドラーを定義できる
'file': {
'level': 'INFO',
'filename': './test.log',
'formatter': 'format1'
},
'console': {
'level': 'ERROR'
'formatter': 'format1'
}
},
'loggers': { # ロガーを定義
'hoge': {
'handlers': ['file', 'console'],
'level': 'INFO'
# ・・・
},
},
}
ファイルローテーションのhandler
handler | 説明 |
---|---|
logging.handlers.RotatingFileHandler |
ログファイルが一定のファイルサイズになったらローテーションする |
logging.handlers.TimedRotatingFileHandler |
特定の時間間隔でローテーションする |
logging.fileConfig
今後の機能追加はlogging.dictConfig
に対して行われているため、利用は非推奨。
18.暗号関連(出題数1:2.5%)
18.3 cryptographyは出題なし
18.1 secretsモジュール
安全な乱数を生成する。
choise関数
import secrets
import string
password_chars = string.ascii_letters + string.digits
password = ''.join(secrets.choise(password_chars) for i in range(8))
print(password)
# 'POVQu190'
token_xxx関数
関数名 | 概要 | デフォルトサイズ |
---|---|---|
secrets.token_bytes(nbytes=None) |
トークン(バイト列)を返す | 32 |
secrets.token_hex(nbytes=None) |
16進数の文字列を返す | 64 |
secrets.token_urlsafe(nbytes=None) |
URL用のテキストとしてBase64エンコードされた文字列を返す | 43 |
トークンの比較は、secrets.compare_digest(a, b)
を使う。
18.2 hashlibモジュール
ハッシュ値(不可逆値)を生成する。
import hashlib
# 利用可能なハッシュアルゴリズムを取得する
print(hashlib.algorithms_available) # 実行プラットフォームに依存する一覧
print(hashlib.algorithms_guaranteed) # 実行プラットフォームに依存しない一覧
# {'sha512', 'sha1', 'md4', ・・・}
hash = hashlib.sha256()
hash.update(b'python library book 2')
hash_value = hash.hexdigest()
print(hash_value)
# 'feb7058093ca35f79...'
ハッシュアルゴリズム | バイト数 | 解説 |
---|---|---|
SHA-1 | 20 | Secure Hash Algorithm-1。脆弱性があり非推奨 |
SHA-256 | 32 | SHA-1の後継であるSHA-2の主要アルゴリズム |
SHA-512 | 64 | SHA-256よりも安全性が高い |
SHA3-512 | 64 | SHA-3アルゴリズムで、SHA-2と同等の安全性 |
BLAKE2b | 64 | 高速なアルゴリズムで64bit環境に最適化されている 32bit環境向けにはBLAKE2sを使用する |
MD5 | 16 | ファイルの整合性検証に使われる。脆弱性があり、暗号化の用途では推奨されていない |
import hashlib
# Python-3.9.6.tgzの期待するハッシュ値
check_sum = '798b9d3e866e1906f6e32203c4c560fa'
hash_md5 = hashlib.md5()
with open('Python-3.9.6.tgz', 'rb') as f:
hash_md5.update(f.read())
print(check_sum == hash_md5.hexdigest())
# True
pbkdf2_hmac関数でソルト(salt)を付ける
import hashlib
password = b'user_password'
salt = b'your_secret_salt'
iteration = 100000 # SHA-256では10万回が推奨
hashed_passwd = hashlib.pbkdf2_hmac('sha256', password, salt, iteration).hex()
print(hashed_passwd)
# '68edb4f6...'
ソルトは、毎回同じものを使うわないようにする。
ソルトは、なるべく長いものを使用する。
19.並行処理、並列処理(出題数0)
pass