0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Python3 エンジニア認定実践試験 チートシート

Last updated at Posted at 2024-12-31
  • 自分の記憶が曖昧なところを中心に作成しています
  • 章番号は公式テキスト「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 .flake8tox.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からtypingimportせずにUnionOptionalが書ける

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) xyの選択

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が必要。
datetimeを合わせたもの。

メソッド 備考
datetime.now(tz=None) タイムゾーンを渡せる
datetime.utcnow() UTC日時を返す
datetime.strftime('%Y/%m/%d') 任意のフォーマットの文字列を返す
datetime.strptime('2021/12/31', '%Y/%m/%d') 任意のフォーマットからdatetimeオブジェクトを作る
strftimestrptimeは動きが逆なので注意
datetime.fromisoformat('2021-12-31') ISOフォーマットからdatetimeオブジェクトを作る
  • date time datetimefromisoformat()にパースできない文字列を渡すと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がある。
pytzfold(サマータイム終了後の巻き戻り期間中を表す属性)には対応していない。

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モジュール

itemgetterの使用例
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.monthdate.dayなど .で属性にアクセスする物をソートする場合はattrgetterを使う。

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}
Counter.elements( )の使用例
from collections import Counter

cnt = Counter('cbbaaa')

print(list(cnt.elements())) # カウントキーのイテレーター
# ['c', 'b', 'b', 'a', 'a', 'a']
Counter.most_common( )の使用例
from collections import Counter

cnt = Counter('cbbaaa')

print(cnt.most_common(2)) # 大きい順に2つ取り出す
# [('a', 3), ('b', 2)]
Counter.update( )の使用例
from collections import Counter

# update()でCounter同士を足す
cnt = Counter('cbbaaa')
cnt2 = Counter('cba')
cnt.update(cnt2)
print(cnt)
# {'c':2, 'b':3, 'a':4 }
Counter.subtract( )の使用例
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で値が同じだから)
異なるEnumとの比較
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']
Keyに比較関数を指定する
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関数

組み込み関数のため、itertoolsimportは不要。
短いほうに合わせる。

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関数

itertoolsimportが必要。

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モジュール

ファイルとディレクトリ操作

後者は中間のディレクトリも作成する。

  • mkdirmkdirs
  • renamerenames
    中間ディレクトリのパーミッションは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

ランダムな文字列を生成

10バイトのランダムなbytesオブジェクトを生成する
print(os.urandom(10))
# b'<10バイト分のデータ>'

randamモジュールは簡易的なものなので、セキュリティを考慮する必要がある場合はos.urandom()もしくはsecretsモジュールを使う。

10.2 ioモジュール

ストリーム 説明
io.StringIOクラス メモリー上でテキストファイルのように扱う
io.BytesIOクラス メモリー上でバイナリファイルのように扱う
  • ストリームをclose()した後に書き込み等を行うとValueErrorが発生する
  • io.BytesIOgetbuffer()は値変更すると元データも変更される
    • 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向け具象パス

純粋パスは実際にファイルがなくても扱える。
Pathpath-likeオブジェクトの一種。

exist_okの例
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で指定したディレクトリ以下を削除する
copytree( )にignoreを指定するサンプル
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&param=bbb')
# {'param': ['aaa', 'bbb']}

# keep_blank_values=Trueの場合
query_dict = parse.parse_qs('param1=&param2=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&param=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'))
ファイルのダウンロード(GET)
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の更新忘れを検出する
doctest_sample.py
"""
与えられた引数を使って割り算を行う。

>>> 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'])
コンテキストマネージャ(with)を使う例
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')とすると、
roothogefugapiyoの階層構造ができる。
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関数

8桁のパスワード文字列を生成する例
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

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?