nikkie-ftnextの日記

イベントレポートや読書メモを発信

"型ヒントの簡単な導入"と言われる「PEP 483 -- The Theory of Type Hints」を読む(その1 Subtype relationships)

はじめに

頑張れば、何かがあるって、信じてる。nikkieです。
この週末(11/14, 15)、PEP 483 -- The Theory of Type Hints を読んでみました。
読んだ時に控えたメモを数回に分けてアウトプットします。

目次

なぜ読んだか

11/20(金)にはんなりPythonでLTが控えています1
PEP 585(組み込みGeneric型2)について、登壇駆動でキャッチアップしようと申し込みました。
LTの準備として、型ヒントまわりのドキュメントを読んでみることにしました。
恥を忍んで言えば、型ヒントはドキュメントを読み込まず、雰囲気でやっている勢3でした。🙈

typingモジュールのドキュメントの冒頭に

型ヒントの簡単な導入は PEP 483 を参照してください。

とあるのを見つけ、PEP 483を読むことにしました。

注意事項

  • 翻訳ではありません。読んだときのメモです。なるべくPEP本体と対応するように進めます
    • 私の理解のためのメモ📝は斜体で追加します
    • 元の文章の斜体は太字にしています
  • 誤読と思われる箇所はコメントやTwitterでお知らせください。大変ありがたいです

検証環境

$ sw_vers
ProductName:    Mac OS X
ProductVersion: 10.14.6
BuildVersion:   18G4032
$ python -V
Python 3.8.6
$ mypy --version
mypy 0.790

PEP 483とは

タイトルは「型ヒントの理論」。
Abstractによると、「PEP484が参照する理論をはっきり説明する」もの。

著者はGuidoさんとIvanさんで、2014年12月に作られました。

Introduction

Python3.5向けの新しい型ヒントのプロポーザルをはっきり説明する(詳細には多く立ち入らない)

説明すること

  1. 理論の基本概念を思い出すことから始める(←この記事ではここだけ扱います
  2. gradual typing(漸進的型付け)
  3. 一般的なルールを述べ、Unionのようなアノテーションで使われる新しく特殊な型を定義する
  4. Generic型(generic types4)と、型ヒントの実用面を定義する

表記の慣例

  • t1, t2u1, u2は型。t1, ... のいずれかとしてtiと表記する
  • TUは型変数(TypeVar()(Document)で定義される)
  • オブジェクト5、class文で定義されるクラス、PEP 8の慣例に従って示されるインスタンス
  • 型に適用される == は、このPEPでは、2つの式が同じ型であることを表す
  • PEP 484では、型は型チェッカー向けの概念で、クラスはランタイムの概念と区別する。本PEPでは区別を明らかにするが、型チェッカーの実装の柔軟性のために不必要な厳密性は避ける

Background

型という概念にはいくつも定義がある

  • 値の集合で定義する(値を列挙)
    • bool型:True, False
  • 値に適用できる関数の集合で定義する
    • Sized型:__len__メソッドを持つオブジェクト6
  • 単なるクラス定義による。intを継承したUserIDクラスの例(このクラスの全てのインスタンスUserIDという型を形成する)
class UserID(int):
    pass
  • より込み入った例:int, strまたはそれらのサブクラスのインスタンスのみを含む全てのリストをFancyList型と定義([1, "abc", UserID(42)]FancyList型)

Subtype relationships(サブタイプ関係)

静的型チェッカーに対して決定的な関係がサブタイプ関係。
次の質問から生じる:
first_type型の変数first_varと、second_type型の変数second_varがあるとき、first_var = second_varという代入は安全(safe)か?

安全であるときの強い基準:

  • second_typeのどの値もfirst_typeの値の集合に含まれる、かつ
  • first_typeのすべての関数7second_typeの関数の集合に含まれる

この関係をサブタイプ関係と呼ぶ。

📝上記は、second_typefirst_typeのサブタイプ関係にある」の定義
📝second_typefirst_typeのサブタイプ関係にあるとき、first_var = second_varという代入は安全である。

上記の定義より、

  • 全ての型は、自身のサブタイプ
  • サブタイプ化が進むに連れ、値の集合は小さくなり、関数の集合は大きくなる

(1) first_typeをAnimal、second_typeをDogとする。
DogはAnimalのサブタイプ(📝 =Dog型の変数をAnimal型の変数に代入できる animal = dog

📝なぜなら、以下のように「second_typefirst_typeのサブタイプ関係にある」の定義を満たすから

  • Dog型(second_type)の値(種々のわんこ🐶)であれば、Animal型(first_type)の値の集合(動物たち)に含まれる
  • Dog型(second_type)はbarkメソッドのようにAnimal型(first_type)よりも多くの関数を持つので、Animal型のすべての関数はDog型の関数の集合に含まれる

逆に、AnimalはDogのサブタイプではない

(2) 整数は実数のサブタイプである。

  • 全ての整数は実数でもある(📝=second_typeのどの値(整数)もfirst_typeの値(実数)の集合に含まれる)、かつ
  • 整数はシフト演算8 << や >> のように多くの演算をサポートする(=📝first_type(実数)のすべての関数はsecond_type(整数) の関数の集合に含まれる

📝つまり「second_typefirst_typeのサブタイプ関係にある」を満たしている

lucky_number: float = 3.14
lucky_number = 42  # intはfloatのサブタイプなのでsafe
lucky_number * 2
lucky_number << 5  # PEP483では「Fails」ですが、mypyはスルー。どういうことでしょう?🤔

unlucky_number: int = 13
unlucky_number << 5
unlucky_number = 2.72  # unsafeなので、error: Incompatible types in assignment (expression has type "float", variable has type "int")

(3) List[int]型はList[float]型のサブタイプではない

List[int] は整数だけを含む全てのリストから構成される型を意味する。
List[float]は実数だけを含む全てのリストから構成される型を意味する。

  • 📝List[int]型の値は、List[float]型の値の集合に含まれる(=second_typeList[int])のどの値もfirst_typeList[float])の値の集合に含まれる)
  • しかし、実数を末尾に追加する関数はList[float]でのみ機能する(→📝first_typeList[float])のすべての関数はsecond_typeList[int])の関数の集合に含まれるわけではない
from typing import List


def append_pi(lst: List[float]) -> None:
    lst += [3.14]


my_list: List[int] = [1, 3, 5]
append_pi(my_list)  # error: Argument 1 to "append_pi" has incompatible type "List[int]"; expected "List[float]"
my_list[-1] << 5  # mypyで検出されるので、実行時のエラーを避けられる

型チェッカーにサブタイプの情報を宣言する方法

型チェッカーにサブタイプの情報を宣言する方法は2つのアプローチが普及している

(A) nominal(名目的) subtyping
クラス木(class tree)に基づくタイプ木(type tree)
例:UserIDはintのサブタイプと見なせる(📝UserIDはintを継承したクラス9だから
Pythonでは互換性のないやり方で属性をオーバーライドできるので、このアプローチが型チェッカーの管理のもとで使われるべき

class Base:
    answer = "42"


class Derived(Base):
    answer = 5  # error: Incompatible types in assignment (expression has type "int", base class "Base" defined the type as "str")

(B) structural(構造的) subtyping
宣言されたメソッドからサブタイプの関係にあるか推論する
例:UserIDとintは同じ型と見なされる
時折混乱を招くが、こちらはより柔軟と見なされている

アプローチ両方とも10にサポートを提供する。
nominal subtypingに加えて、 structural information(構造的な情報)も利用できる

終わりに

1の内容は以上になります。
Generic型の共変・反変を除いて、一通り読んでいるので、時間を見つけて続きを上げていきます。

サブタイプ関係にあるかどうかを考えていると、大学時代の数学の講義を思い出しました。
ふだん使っているプログラミング言語と学問体系のつながりの一端を実感できました。


  1. お時間ありましたらどうぞ。Python3.9について語るLT会です 【オンライン】はんなりPython #34 python3.9を語る LT会 - connpass

  2. python.jpの Python 3.9の新機能 - python.jp が分かりやすいと思います。PEP 483や他のドキュメントと行き来して、ようやく分かってきました

  3. ドキュメントを読むと、アンチパターンに手を出していたことが分かりました。知らないって怖いですね

  4. genericの訳が難しいですね。python.jpにならって「Generic型」としています。typingのドキュメントでは「ジェネリクス」となっているところもあります

  5. このObjectsは何がかかるのか今ひとつ分かっていません

  6. https://docs.python.org/ja/3/library/collections.abc.html#collections.abc.Sized

  7. 原文はevery function from first_type。メソッドも関数と理解しています

  8. https://docs.python.org/ja/3/reference/expressions.html#shifting-operations

  9. BがAを継承している(BはAのサブクラス)とき、「B型のどの値もA型の値の集合に含まれ」、かつ、(継承しているから)B型に適用できる関数はA型より増えるのでサブタイプ関係にあると結論付けられますね

  10. この部分の強調はnikkieによる

週末ログ | PyTorch Lightningの"Lightning in 2 steps"を触りました⚡️

はじめに

頑張れば、何かがあるって、信じてる。nikkieです。
最近v1.0が出たPyTorch Lightning、Getting startedのドキュメントに沿って週末に素振りしました。

目次

PyTorch Lightningとは

世はまさに大AI時代といった趣で、TensorFlowをはじめとする深層学習フレームワークが盛んに開発されています。
その中に、Facebook発の深層学習フレームワーク PyTorch があります。
PyTorch Lightning(以下、Lightning)はPyTorchのラッパーライブラリです。

PyTorchで頻繁に書くボイラープレートのコードがなくなるように設計されています。
機械学習スクリプトエントリポイントが劇的に薄くなるので、「めっちゃイケてる✨」と心ときめきました。
実験に使うスクリプトのエントリポイントが長大になりがちなんですよね。。

if __name__ == "__main__":
    autoencoder = LitAutoEncoder()
    
    dataset = MNIST(Path.cwd(), download=True, transform=transforms.ToTensor())
    train_loader = DataLoader(dataset)

    trainer = pl.Trainer()
    trainer.fit(autoencoder, train_loader)

10月のv1.0.0リリースに合わせて開発チームからBlogがポストされています。

  • v1.0.0でLightningのAPIがfix
  • Metricクラス追加
  • 劇的に簡単なlogging!(self.log

開発チームはGrid AIというモデルの訓練・サーブプラットフォームも開発していくそうです。

Lightningの哲学

LightningはPyTorchのラッパーなので、書き換え方を押さえなければなりません。
使い始めるのに必要な学習コストが気になりますが、リポジトリのREADMEで紹介されている原則(Lightning Philosophy)がヒントになると思いました。

Principle 4: Deep learning code should be organized into 4 distinct categories.

  • Research code (the LightningModule).
  • Engineering code (you delete, and is handled by the Trainer).
  • Non-essential research code (logging, etc... this goes in Callbacks).
  • Data (use PyTorch Dataloaders or organize them into a LightningDataModule).

先のコードでは、LitAutoEncoderLightningModuleクラスを継承したResearch codeです。
Engineering codeは全てTrainerに寄せています。

以下、素振りで作ったコードを備忘録代わりに残します。

今回の環境

$ sw_vers
ProductName:    Mac OS X
ProductVersion: 10.14.6
BuildVersion:   18G4032
$ python -V
Python 3.8.1
$ pip install pytorch-lightning torchvision
# pytorch-lightning      1.0.3
# torchvision            0.7.0

# -- Lightningの依存により以下が入った ---
# numpy                  1.19.2
# torch                  1.6.0
# tensorboard            2.3.0

Lightning in 2 stepsのコード

冒頭で紹介したページのMNISTの例(オートエンコーダー訓練)を進めました。
ページ冒頭の3分動画も参考にしています。

from argparse import ArgumentParser
from pathlib import Path

import pytorch_lightning as pl
import torch
import torch.nn.functional as F
from torch import nn
from torch.utils.data import DataLoader, random_split
from torchvision import transforms
from torchvision.datasets import MNIST

Research code: LightningModule

class LitAutoEncoder(pl.LightningModule):
    def __init__(self):
        super(LitAutoEncoder, self).__init__()
        self.encoder = nn.Sequential(
            nn.Linear(28 * 28, 64),
            nn.ReLU(),
            nn.Linear(64, 3),
        )
        self.decoder = nn.Sequential(
            nn.Linear(3, 64),
            nn.ReLU(),
            nn.Linear(64, 28 * 28),
        )

    def forward(self, x):
        embedding = self.encoder(x)
        return embedding

    def configure_optimizers(self):
        optimizer = torch.optim.Adam(self.parameters(), lr=1e-3)
        return optimizer

    def training_step(self, batch, batch_idx):
        x, y = batch
        x = x.view(x.size(0), -1)
        z = self.encoder(x)  # forwardを呼び出す self(x) でもよい
        x_hat = self.decoder(z)
        loss = F.mse_loss(x_hat, x)
        self.log("train_loss", loss)
        return loss

    def validation_step(self, val_batch, batch_idx):
        x, y = val_batch
        x = x.view(x.size(0), -1)
        z = self.encoder(x)
        x_hat = self.decoder(z)
        loss = F.mse_loss(x_hat, x)
        self.log("val_loss", loss)
        return loss

ポイントと思ったところ1

  • LightningModuleは1つのモデルではなく、複数のモデルからなるシステムを扱える2(この例でもencoderとdecoderを扱っている)
  • forwardメソッドとtraining_stepメソッドを分離3forwardはデプロイしたモデルの推論でも使える)
  • training_stepメソッド(訓練)でlossを返している限りはLightningによって自動で最適化される(backward、optimizerの更新)

Engineering code: Trainer

  1. 訓練時の設定(GPUを使うかなど)を元にTraninerを初期化し、
  2. Trainerにモデル(LightningModuleを継承したクラス)とデータを渡して訓練します

1のTranierの初期化は、コマンドラインからも指定できます4
Trainer初期化の引数をいじらなくていいので便利そうです。

def parse_args():
    parser = ArgumentParser()
    parser = pl.Trainer.add_argparse_args(parser)
    args = parser.parse_args()
    return args


if __name__ == "__main__":
    args = parse_args()
    
    trainer = pl.Trainer.from_argparse_args(args)

コマンドラインからTrainerに渡すと便利そうだと思ったオプション

  • --gpus 個数
    • to(device)というPyTorchで頻出するコードから解放されたのはすごい!
  • --fast_dev_run:コード全体のユニットテストオプション5
    • single batchのtrain, val, testデータで実行
    • これにより、つまらないミスで訓練中に落ちる悲しい事件とさようならできる!
  • --limit_train_batches バッチ数, --limit_val_batches バッチ数:少量のデータで実行
  • --max_epochs, --max_stepsも設定できる
  • --deterministic:再現性の担保6
    • seed_everything関数と一緒に使う(numpy, torch, random, PYTHONHASHSEED全部のシードを固定)

Data: PyTorch Dataloader & LightningDataModule

train, val, testの各データの扱いをエントリポイントから、データモジュールクラスに移せるのが好感触です。

class MNISTDataModule(pl.LightningDataModule):
    def __init__(self, batch_size=32):
        super(MNISTDataModule, self).__init__()
        self.batch_size = batch_size

    def prepare_data(self):
        MNIST(Path.cwd(), train=True, download=True)
        MNIST(Path.cwd(), train=False, download=True)

    def setup(self, stage):
        if stage == "fit":
            mnist_train = MNIST(
                Path.cwd(), train=True, transform=transforms.ToTensor()
            )
            self.mnist_train, self.mnist_val = random_split(
                mnist_train, [55000, 5000]
            )
        if stage == "test":
            self.mnist_test = MNIST(
                Path.cwd(), train=False, transform=transforms.ToTensor()
            )

    def train_dataloader(self):
        mnist_train = DataLoader(self.mnist_train, batch_size=self.batch_size)
        return mnist_train

    def val_dataloader(self):
        mnist_val = DataLoader(self.mnist_val, batch_size=self.batch_size)
        return mnist_val

    def test_dataloader(self):
        mnist_test = DataLoader(self.mnist_test, batch_size=self.batch_size)
        return mnist_test
if __name__ == "__main__":
    args = parse_args()

    autoencoder = LitAutoEncoder()

    dm = MNISTDataModule()

    trainer = pl.Trainer.from_argparse_args(args)
    trainer.fit(autoencoder, dm)
    # trainer.test(後ほど試す) 

Non-essential research code: Callbacks

今回は手を動かしていませんが、early stoppingはCallbackで実現するそうです。

logging7self.logに指標名と一緒に渡すだけ。
プログレスバーtensorboardで確認できます(logの引数で調整もできます)。

tensorboard --logdir ./lightning_logs

lightning_logsディレクトリにチェックポイントが保存されています

感想

エントリポイントが薄くなった高揚感でここまで書き上げました。
Lightningはすごく有用そうです!
ただ自動最適化はブラックボックス化でもあるので、PyTorchのボイラープレートコードも理解し、Lightningで賢く楽をしたいですね。

今後は実際に学習を回して素振りを繰り返し、ドキュメントで知ったことを知識に変えていこうと思います。
チュートリアルのMNIST 60000件はCPUでは訓練がサクサクいかないので、データを間引くか、他のデータセットを探すかですね。

訓練したモデルはtorchscriptなる形式で掃き出せるそうです。
この週末DeNA AIチャンネルで知って、よさそうに感じたstreamlitでdecoderをアプリ化しても面白そうだなと思いました。

イベントレポート | オンライン開催のみんなのPython勉強会#62(若手を応援!回)にスタッフ参加しました #stapy

はじめに

頑張れば、何かがあるって、信じてる。nikkieです。
10/14の「みんなのPython勉強会」にスタッフ参加してのレポートです。

目次

勉強会の概要

みんなのPython勉強会#62 - connpass

「みんなのPython勉強会」では、Pythonを中心としてプログラミングを仕事、研究、趣味など様々なシーンに生かす方法を一緒に学びます。

今回の企画についてはスタッフのあべんべんさんがnoteにまとめています。

一部引用すると

こういった経験(※引用者注:大人数の前でのアウトプット)をできるだけ若手にも体験させたいし、ベテランも若手からいろいろ学びたいのです。

勉強会の様子

YouTube アーカイブ

Togetter

勉強会の裏側(How nikkie worked as staff)

数ヶ月前の宿題:待機中のBGM🎶

6月にお品書きスライド再生をしたところBGMの要望をいただいた1ので今回試してみました。
Zoomで画面共有するときに「コンピュータの音声を共有」を有効にします。
画面を共有 – Zoom ヘルプセンター
画面共有をしている状態で、macOSiTunesで用意していた音源を流しました。

Zoomに流れているかはチャットで聞いたところ回答があり大変ありがたかったです。
今思うと別端末でZoomに入って確認する方法もありましたね。

今回は以下の音源を使いましたが、先日Django Girlsで色々教えていただいたので他のも試してみたいです。
フリーBGM(音楽素材)無料ダウンロード|DOVA-SYNDROME

参加者が一時チャットできないトラブル😰

「今日はチャットに8888があまり流れないな」と思っていたら、面識のある参加者の方から「参加者がチャットできない」と連絡が。
ホスト・共同ホスト(=スタッフ)以外がチャットできない設定に切り替わっていたようです。 ミーティング内チャットのコントロールと無効化 – Zoom ヘルプセンター

自分が使えている機能が他の人には使えないかもと想定するのは難しいですね。
これも別端末でZoomに入れば気づけるかもしれません。
個人的には、どこかのレポートで書いたZoomの設定API向けのconfigを用意して、コードで解決したい気持ちです(フィジビリティから要検証ですが)。

とりわけ印象に残ったトーク🤩

Hakaliのお二人のトークが印象に残りました!

池内さん基調講演『持続可能なエンジニアであるための3つの心構え』

懇親会で「どうしてPythonを?」という話になり、池内さんはPyCon JPに参加してPythonコミュニティの雰囲気が面白いと感じPythonコミュニティに関わり始めたとのことでした。
ご自身の経験を踏まえての「サードプレイスとなるコミュニティ」ですね!

秦さん15分トーク『データ未経験僧侶が2ヶ月でPythonで予測モデルを作れるようになるまで』

こちらのnoteをベースにしたトークでした。

個人的に刺さったのは、『自灯明・法灯明』の話。

プログラミングも、まず自分がやりたいことを理解して、法=正しい進め方を説いてくれるブッダ的存在を見つけて頼りながら確かに歩んでいきましょう!2

秦さんはプログラミングを始めた方を念頭に話されていたと思うのですが、入門者という立場からは先に進んだプログラマ「すみません!この方向で合ってますかー」と誰かに聞きたくてもがいているんじゃないかと思いました。

私の独学のモチベーションは、失敗を繰り返さない(次にうまくやるにはどうするか)というところにあります。
誰かがうまくやっているという情報を本でもWeb記事でも登壇でも、日本語でも英語でも漁って収集しています。
これって突き詰めると「次はもっとうまくやりたくて、でも方向性には自信がなくて、『誰かこの方向で合っているか教えて下さい!』と聞きたい」ってことなのかなという気付きがありました。

ただ求めている誰かがいるとは限らない(まだ明確な答えを持っている人がいないかもしれない)ので、あの手この手で情報漁りは続きます。
知りたいという自分の灯り(自灯明)を元に暗中模索する中で、方向性を教えてくれるような情報源(法灯明)に出会うと、「自信がなかった仮説はまるっきり見当違いじゃなさそう」と安心したり、「そうか、この方向性で考えればいいのか!」とめちゃくちゃテンションが上がったりします。
直近ではPyCon Africaのいくつかのトークを聞いて経験しました!

秦さんの15分トークはプログラミング×仏教で新たな視点を得られたので、オススメです!(アーカイブをどうぞ)

積ん読資料📚

ミニトーク『回帰分析における残差の正規性 目的変数の自然対数変換』

登壇者の山下さんが参考にした資料 http://www.u.tsukuba.ac.jp/~hirai.akiyo.ft/forstudents/HP%20title/ibunka2014/20140702Ch.8.pdf

発表への補足ツイート3で知った資料。
ものすごい詳しくて必読感があります。

三森さんトーク関連、GANまわり

Slidoの質疑やZoomのチャットで見かけたのですが、GANを語る時はG(Generator), D(Discriminator), Zなど独特ですね(数式表現から来ているんですかね)。

Zoomのチャットによるとキャッチアップには以下がオススメのようです。

終わりに

10月のstapyについて、印象に残ったトークのアウトプットをしました。

11月は12日に開催です。
みんなのPython勉強会#63 - connpass

内容はまだ公開していませんが、よろしければお越しください!
懇親会のLTもお待ちしています!!
(9月から複数人LT登壇があり、4代目LT王子としては大変嬉しく思っています。接続中4LT王子誕生に期待しています)

LT補足: Stay home, but connect global Python users! #pyconhiro

はじめに

頑張れば、何かがあるって、信じてる。nikkieです。
10/10のPyCon mini Hiroshimaで、海外PyConにオンライン登壇したこの夏の学びを共有しました。
LTの制限時間5分に収め切れなかった内容を補足としてアウトプットします。

目次

PyCon mini Hiroshimaとは

PyCon mini Hiroshima 2020は、“つながろう Python”がテーマです。
3時間にぎゅぎゅっと詰め込んだオンラインカンファレンスとして開催されました。

その歴史は古く、2015年から開催されています(2017年を除く)。

この夏オンライン登壇して各地のPython使いとつながったというLTネタが浮かび、登壇しました。

LT: Stay home, but connect global Python users!

海外のPyConへの参加・登壇への挑戦で得られたことを共有しました。

対象者には、オンライン登壇する前の私のような方を想定しています。

  • Pythonについて日本語で登壇した経験がある(Python中級者〜熟練者)
  • 海外PyConには興味もある
  • ただし英語が不安(話す・聞くに自信がない)
  • PythonPythonパッケージのドキュメントは普段から英語でも読んでいる1

伝えたかったことは次の2点です。

  1. 英語での登壇に不安がありましたが、読む・書く・聞く・話すの「読む」を重視し、ツールを使って原稿を用意するという作戦で、不安を突破しました。
  2. 英語でオンライン登壇しての学びは、登壇して何かを伝えるのに重要なのは「熱」ということです。好きな技術への想いは、英語の準備がまだ十分にできていないと尻込みせずに、自身が熱を失わないうちに、伝えるのがオススメです。

LTには4つのことを詰め込みました。

  • なぜ海外PyConで登壇しようと思ったか
  • 英語の不安を解消するための準備
  • 英語でオンライン登壇して得たもの
  • 海外のカンファレンスに参加して得たもの

詰め込みきれなかった点を補足します。

なぜ海外PyConで登壇しようと思ったか

2020年は「US PyConにプロポーザルを通す」という"アホ"な目標を掲げています2
US PyConは狭き門なので、これはなかなか困難な(実現させるのに発想を変える必要がありそうな)目標です。
一発勝負では分が悪いことは痛感していたので、何回も勝負してプロポーザルを磨くことにしました。
プロポーザルにフィードバックをもらう機会として、各地のPyConにプロポーザルを投稿しています(今年の3月〜5月のことです)。

英語の不安を解消するための準備

英語のドキュメントは(辞書を使いながら)読みますが、書く・聞く・話すには不安がある身です。

「書く」不安の解消

原稿を用意するには「書く」必要があります。
ここはツール(DeepLGrammarly)の力を使い3日本語を書き、英語は読んで整えるという方法で英語を直接書かずに取り組みました。

  1. 日本語で原稿を用意(文や段落単位)
  2. DeepLで翻訳
  3. 翻訳を読んで、単語を置き換えたり、語順を変えたり修正(訳がいまいちだったら1に戻る)
  4. DeepLの訳をまとめてGrammarlyに投入。文法ミスを直す

単語を調べるときは、macOS辞書.app4が非常に便利です。
英和も和英も1つのアプリで使えて助かっています。

登壇中、別チャンネルで「5 min left」などタイムキープの英語音声が聞こえることがありました。
タイムキープが来ると知っていても私は結構驚くので、原稿があってよかったです。
突然の英語で頭が真っ白になりながらも、目の前の原稿読み上げに集中して、話し続けていました。

「話す」不安の解消

ジャパニーズイングリッシュでなまっている自覚はあります。
去年Singaporeに行った経験5から、なまった発音でも伝わると知っていたので、ジャパニーズイングリッシュを堂々と貫くことにしました6

今回は時間が割けずにやりませんでしたが、macOSsayコマンドで練習というアイデアもあります。

「聞く」不安

今回はほとんど対策しませんでした。
オンライン登壇ということでテキストチャットでの反応や質疑が多かったです。
また、聞き取れなくても「チャットでお願いします」でなんとかなるだろうと考えていました。

過去に海外PyConに行ったときはOtterというアプリを使いました7

英語の「読み」の重要性

この夏の経験から、英語が読めることの重要性を痛感しました。

技術の進歩はめざましく、翻訳ツールを使えば、英語は一応書ける時代です。
ですが、英語が読めないとそのツールに使われてしまうと感じています。
逆に言えば「読め」さえすれば、私の例のように、「書く」「聞く」はツールを味方にできます
「正確に読むための努力は今後もしていきたいな」と思っています8

英語でオンライン登壇して得たもの

不安のあった自分の英語でも届いた!」という感覚を得ました!
他の方の登壇と比べて質疑やリアクションが少ないかもしれません。
ですが、「反応いただけた方たちには届いたんだ!」と達成感を感じています。

では、届いた要因はなにかですが、「この技術について伝えたいことがある」「Pythonでこんなことをやっていることを伝えたい」というだと思っています。
「海外カンファレンスでの登壇に興味がある。でも、英語が十分に話せないから躊躇してしまう」という方には、 ぜひ自分のに任せて、ツールの力を借りてやってみることをオススメします。
さしずめ「英語を完璧に準備することを目指すより、まず登壇してみよう」9といったところでしょうか。

英語の間違いはコワくありません。
例えば、今回のLTのタイトルですが、「世界のPythonユーザとつながろう」だったら、「Connect with other Python users around the world」の方が正しそうです。
こういった誤りはこの夏何度も犯していると思います。
ただ結果的には伝えられたと思っているので、ちょっとした間違いは気にしないというスタンスでいいと思っています。

なお、「熱」ですが、偶然にもそーだいさんの直近のアウトプットと重なりました。

一番大事なことは情熱

そーだいなるキャリアを作った、そーだいなる登壇資料の作り方 - そーだいなるらくがき帳

海外のカンファレンスに参加して得たもの

海外PyConに参加して、2種類の新たなつながりを得ました。

  1. Pythonとのつながり
  2. 各地のコミュニティとのつながり

Pythonとのつながり

各地のPyConは、Pythonの本質的なトピックについてのトークが多かったという感想です。
PyCon JPは「Pythonでこんなことができる」というバラエティに富んでいるので、これは対照的でした。
例えばメモリ管理やPythonオブジェクト指向といった話を聞いて、Pythonとのつながりが深まったと感じています。

Pythonとのつながりを深める機会を提供してくださった何人ものPython使いは、言ってみれば「ヨーダ10のような存在です。
こういったPython使いとも(一方向ですが)つながれて、私のモチベーションは上がりました。
メルマガを購読するなど、インプットに加えています。

PythonとDocker ベストプラクティス

Best practices for production-ready Docker packaging — EuroPython 2020 Online · 23-26 July 2020

Generatorについて

Reuven M. Lerner's Profile | PyCon Africa 2020

各地のコミュニティとのつながり

特筆すべきは、私と似た取り組みをしているPython使いとも出会いました!
AlyさんはChicagoのPythonコミュニティ(ChiPy)の共同オーガナイザで、SlackBotを使ってChiPyを盛り上げています。

Aly Sivji's Profile | PyCon Africa 2020

ChiPyを知った私は、リポジトリをウォッチしたり、月例会に参加したりしています。

終わりに

PyCon mini HiroshimaのLTの補足をお届けしました。
海外に渡航が難しいStay homeな世の中ですが、海外PyConにオンライン参加したら素敵な出会いが待っていました
登壇に挑戦するもよし、登壇せずに参加するもよしだと思います。
このLTと補足資料をきっかけにお一人でも海外PyConに興味を持ち参加していただけたら嬉しいです。
各地のPyConの発表はYouTubeにあるので、探してみるのもオススメですよ!


  1. PythonパッケージはGetting startedから進もうとすると日本語訳が減っていく感じがあります。PEP(Python Enhancement Proposals)も公式の日本語訳はないと思います。そんなわけでわからない語を辞書で引き、コードをガイドにしながら読んでいます

  2. 近況報告:2020夏、登壇の夏、予定していた全公演を駆け抜けました!🎤 - nikkie-ftnextの日記

  3. Langsmithというツールを最近知りました。今後試すかもしれません ref: https://twitter.com/ttk_kuribayashi/status/1313419014891139072?s=20

  4. WEB+DB PRESS の名前付け特集で知って以来、辞書.appは愛用しています

  5. Event Report | PyCon Singapore 2019 Day1 #pyconsg - nikkie-ftnextの日記

  6. ジャパニーズイングリッシュでも気にしないと思えた要因には、Twitterで知ったエピソードがあります。C・ロナウド「なぜ笑うんだい?」日本人少年の拙いポルトガル語に笑いが漏れた会場でナイスガイな対応!この時の少年の現在が凄い・・・ | FUNDO

  7. こちらで知りました:第2回 2日目「1994年のPython」,日本から参加のポスターセッションとライトニングトーク:ヨーロッパのPythonコミュニティと交流できる3日間「EuroPython 2019」参加レポート|gihyo.jp … 技術評論社

  8. この夏読んだブログに耳が痛い助言があったのですが、そのとおりだなと思います。「英語がちゃんと読めるようになって始めて自分の専門的技術力を高めるスタート地点に立てるのです。」 ref: 社内勉強会で専門的技術力を高めるには - Yahoo! JAPAN Tech Blog

  9. 「完璧を目指すより、まず終わらせろ」(Done is better than perfect)の言い回しを意識しました

  10. SOFT SKILLS』にある言い回しです

イベントレポート | #pyhack にてArgoを触りました(MLOpsに使っていきたいなと素振り🏏しました)

はじめに

頑張れば、何かがあるって、信じてる。nikkieです。
MLOpsにやや興味があり、最近はGitHub ActionsでMLOpsという以下のブログ記事を真似しています。

ここで登場したArgoなるものが気になり、9月の Python mini Hack-a-thon で触ってみました。

目次

勉強会の概要

(第115回)Python mini Hack-a-thon(オンライン) - connpass

スプリントのゆるい版みたいな感じで各自自分でやりたいことを持ってきて、勝手に開発を進めています。

Twitterの #pyhack ハッシュタグを追うと雰囲気がつかめると思います。
集中してもくもくできて素振りが捗りました。
1日お疲れさまでした。ありがとうございました!

Argoとは

Container-native Workflow Engineを標榜するArgo。
ワークフローの各ジョブはKubernetesk8s)上で動かせます。
今日触った範囲では、コンテナをつなげてワークフローを定義し実行できるという理解です。
ワークフローの各ジョブはコンテナ化されているので、異なるプログラミング言語で書けるというのが1つのメリットなのかなと思いました(マイクロサービスっぽい!)。

k8sクラスタAWSGCPに立てるのは素振りにはちょっとお高いので、minikubeを使ってローカルで動かしました。

取り組んだこと

  • ローカル環境構築
  • Argoのexampleで概念を掴む
  • GitHub Actionsで動かしているCIワークフローをArgoで動かしてみる

先日書いたGitHub ActionsでMLOpsの記事でやっているスクリプト実行がArgoでできました!

ローカル環境構築

minikube

minikube start | minikube の手順でv1.12.3をインストール。
過去にbrewで入れたv1.9系は動かなくなっていたので、アンインストールして新しいものにしました1

minikubeコマンド自分用メモ

$ minikube start
$ minikube pause
$ minikube unpause  # pauseした後の再開
$ minikube stop

Argo

minikube startしてある状態で以下を進めます。
Quick Start - Argo Workflows - The workflow engine for Kubernetes

kubectl port-forwardすると、ブラウザからArgoを設定できます。
(こちらで操作もできるようです2が、今回はyamlを書いて進めていきました)

f:id:nikkie-ftnext:20200926203657p:plain

Argo CLI

ArgoのQuick startで案内されているCLIも入れます。
Release v2.11.0 · argoproj/argo · GitHub をもとにmacOSにインストールしました。

Argo CLIkubectlのラッパーというのがポイントでした。
argoコマンドもkubectl同様-nnamespaceが指定できます。
これを指定しないと、~/.kube/configの指定によりminikube namespaceを操作するので、ワークフローがうまく実行されませんでした。

Argoのexamplesで概念を掴む

examplesのREADMEで手厚く解説されています。

READMEの一部を写経

READMEの上から

  • Hello World!(docker/whalesayを動かす)
  • Parameters(docker/whalesayに引数を渡す)
  • Steps(順次実行、並列実行)
  • Artifacts(あるコンテナの出力を次のコンテナの入力へ)

を進めました。

Argoはk8sのカスタムリソースとして定義されているので、k8sと同様にyamlファイルを書けば済みます。
k8sは業務で触っているので、kind: Workflowを指定したyamlファイルを書くというのは、結構しっくり来ました。
どんなパラメタで、また、どうファイルマウントしてDockerイメージを動かすかyamlで書いていく感じです。

argoコマンド チートシート

※自分用まとめです

  • argo lintyamlを書いた直後に、yamlの形式に異常がないか確認するのに使います
  • argo list:ワークフローの一覧
  • argo submityamlをもとにワークフローを作成して実行(--watchを付けて実行状況を監視できます3
  • argo get:指定したワークフローの詳細が見られます
  • argo logs:指定したワークフロー全体や各ステップ(ポッド)のログが見られます
  • argo delete:指定したワークフローの削除

行き着いたコマンド順(-n argoはnamespaceの指定です)

$ argo -n argo lint argo-workflow/practice.yaml
$ argo -n argo list
$ argo -n argo submit argo-workflow/practice.yaml --watch

STEP                       TEMPLATE          PODNAME                            DURATION  MESSAGE
 ✔ artifact-passing-v6rnm  artifact-example
 ├---✔ generate-artifact   whalesay          artifact-passing-v6rnm-1548704093  5s
 └---✔ consume-artifact    print-message     artifact-passing-v6rnm-1648314195  7s

# ワークフロー全体のログを見る
$ argo -n argo logs artifact-passing-v6rnm

# ワークフローの中の generate-artifact ステップだけのログを見る
$ argo -n argo logs artifact-passing-v6rnm artifact-passing-v6rnm-1548704093

$ argo -n argo delete artifact-passing-v6rnm

GitHub Actionsで動かしているCIワークフローをArgoで動かしてみる

以下のリポジトリGitHub Actions製ワークフロー(Pythonのunittestを実行)を
bring-script-more-users/python-unittest.yml at master · ftnext/bring-script-more-users · GitHub Argoで動かしてみます。
参考にしたのはこちら。

パブリックリポジトリにて動作

以下の2ステップで実装しました4

  1. checkout:git cloneしてソースコード取得5
  2. test-unit:依存をインストールしてからユニットテストを実行
apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
  generateName: bringscript-ci-
spec:
  entrypoint: bringscript-ci  # templatesの中のbringscript-ciを実行する指定
  arguments:  # 引数をデフォルト値込みで設定
    parameters:
    - name: repo
      value: https://github.com/ftnext/bring-script-more-users.git
    - name: revision
      value: bringscript0.1.0

  templates:
  - name: bringscript-ci
    steps:  # 順次実行する書き方
    - - name: checkout
        template: checkout
    - - name: test-unit
        template: test-unit
        arguments:
          artifacts:  # 取得したソースコードのディレクトリをバインド
          - name: source
            from: "{{steps.checkout.outputs.artifacts.source}}"

  - name: checkout
    inputs:
      artifacts:
      - name: source
        path: /src
        git:
          repo: "{{workflow.parameters.repo}}"
          revision: "{{workflow.parameters.revision}}"
    outputs:
      artifacts:
      - name: source
        path: /src
    container:
      image: python:3.8
      command: ["/bin/sh", "-c"]
      args: ["cd /src && git status && ls -l"]

  - name: test-unit
    inputs:
      artifacts:
      - name: source
        path: /python/src/github.com/ftnext/bring-script-more-users
    container:
      image: python:3.8-slim-buster
      command: ["/bin/sh", "-c"]
      args: ["
        cd /python/src/github.com/ftnext/bring-script-more-users/bringscript &&
        python -m pip install .[dev] &&
        pytest
      "]

checkoutステップはgit pullできればいいので、Dockerイメージはpython:3.8を指定。
test-unitステップはpythonだけあればいいので、python:3.8-slim-busterを指定しています。

プライベートリポジトリの場合は?

k8sSecretsを作って、プライベートリポジトリにアクセスするための情報をワークフローから参照します。

kubectl -n argo create secret generic github-creds --from-file=ssh-private-key=<SSH秘密鍵のパス>

ssh-private-keyの部分がSecretsのキーになります。
指定していないとファイル名がキーになるという挙動でちょっとハマりました6

# ... snip ...
  - name: checkout
    inputs:
      artifacts:
      - name: source
        path: /src
        git:
          repo: "{{workflow.parameters.repo}}"
          revision: "{{workflow.parameters.revision}}"
          sshPrivateKeySecret:  # 追加
            name: github-creds  # 追加
            key: ssh-private-key  # 追加
    outputs:
      artifacts:
      - name: source
        path: /src
    container:
      image: python:3.8
      command: ["/bin/sh", "-c"]
      args: ["cd /src && git status && ls -l"]
# ... snip ...

f:id:nikkie-ftnext:20200926203642p:plain

GitHub ActionsとArgoの違い

ステップの分け方が違うことに気づきました。

GitHub Actionsは1つの環境(ランナー)で複数のステップを実行します。
そのため、依存のインストールとユニットテストの実行を別ステップに分けることができます

Argoはさまざまな環境(コンテナ)でステップを実行していきます。
そのため、依存のインストールとユニットテストの実行を分けずにPythonが使えるコンテナの中に押し込むことになりました。

次にやること

今回Argoを触り、例のGitHub ActionsでMLOpsブログの真似は先に進めそうです。
AWSGCPなどクラウド上にクラスタを作り、Argoをセットアップ、そしてGitHub ActionsからArgoを呼び出すというのを今後取り組んでみようと思います。

おまけ:Dockerまわりのコンマリ

minikubeの再設定まわりでつまづき、「もしかしてDockerのDisk image size?」と見てみました(これは見当違いでした)。
macOSのDockerは定期的にコンマリする(片付けする)しかないんですね。
Docker向けに割り当てたDiskが減るたびに暫定対応として割り当てを増やしていたのですが、Docker.rawが巨大になり、ディスクを圧迫していました。

Disk image sizeを広げていくのはアンチパターンだと考えます。
上限に達したら、sizeを減らすしかなくなり、そのときにDockerイメージが全部消えます。
大量のDockerイメージの中から消してはいけないものがないか確認するよりも、定期的にコンマリしたほうがいいなと今回体験しました。


  1. ~/.minikube~/.kube/configに残っていた古い情報のためにminikube startに失敗したようなので、これらの削除も必要でした。

  2. Private Repositories - Argo CD - Declarative GitOps CD for Kubernetes など(Argo CDというのは今回触ったArgoとは別物なのかな?)

  3. kubectl-wと違ってwatchsubmitサブコマンドだけみたいです

  4. spec.entrypointに指定したtemplateが実行されます。このtemplateの中でcheckout→test-unitと順次実行する実装です(プログラミングの関数っぽいですね)

  5. artifactのgitの指定についてはドキュメントのこのあたりにありました

  6. SSH秘密鍵パスフレーズが指定されていたためにエラーになったので、Secretsに設定する際はssh-keygenで再設定して外しました。 ref: sshの秘密鍵のパスフレーズを削除する - Qiita

GitHub ActionsでMLOpsをやりたくて、サンプルワークフローのChatOpsの仕組みを読み解きました

はじめに

頑張れば、何かがあるって、信じてる。nikkieです。
データサイエンティストとしてPythonを書く中で、徐々にMLOpsに興味が出てきました。
GitHub ActionsでMLOpsしているブログ記事を知り、「私もやりたい!」とその中のChatOpsの部分を読み解いてみました。

目次

GitHub ActionsでMLOpsの例

GitHub Blogに以下の記事があります。

ここで示されている例は、pull request上でコメントすると、GitHub Actionsのワークフローのうちコメントに対応するものが実行されるというものです。

  • pull requestに /run-full-test を含むコメントを投稿すると、Dockerイメージのビルドが走り、モデルが評価されます(結果がpull requestにコメントされます)
  • pull requestに /deploy を含むコメントを投稿すると、コメント内で指定したRun IDのモデルがデプロイされます

百聞は一見に如かず、以下も合わせてご覧ください。

私はこの例を見て、
「SlackのスラッシュコマンドのようにGitHub Actionsのワークフローを呼び出せるのかっこいい!✨
これがあったら、ブランチを切って仮説に基づく変更をプッシュするだけで試行錯誤できそう。やってみたい!🤩」
とめちゃくちゃテンション上がりました!
なお、pull requestにコマンドを入力することでワークフローを実行できるので、私はChatOpsと呼んでいます1

手元のリポジトリで再現させるまで

GitHub Action: ChatOps For Pull Requests

イシューへのコメントissue_commentドキュメント)で発火するワークフローを作ります。
このワークフローはコメントに含まれるコマンドを元に、後続のワークフローをディスパッチします。
1つのワークフローで全てをまかなっているわけではありませんでした。

ワークフローのステップには、以下のmachine-learning-apps/actions-chatops@masterを使います。

      - uses: machine-learning-apps/actions-chatops@master
        with:
          TRIGGER_PHRASE: "/test-trigger-comment"
          INDICATOR_LABEL: "test-label"
          APP_PEM: ${{ secrets.APP_PEM }}
          APP_ID: ${{ secrets.APP_ID }}
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

/test-trigger-commentが含まれていれば、machine-learning-apps/actions-chatops@masterが実行されます。
このActionでやることはINDICATOR_LABELラベルを当該プルリクエストに付けることです。
ラベルの付加をトリガーに後続のワークフローが実行されることで、ディスパッチが実現されます。

APP_PEM, APP_ID? → GitHub Apps

GitHubには疎いので、APP_PEM, APP_IDはピンときませんでしたが、GitHub Appsというものがあるそうです。
今回手を動かした範囲ではSlackのSlack Appと同様のものという理解です。
プルリクエストにラベルを付けるといった操作をコードから行うときに、必要な権限にアクセスできるGitHub Appを作るそうです。
それを当該リポジトリにインストールして使うという仕組みになっている2ことを今回知りました。

GitHubのヘルプページに沿ってGitHub Appを作成します。

Appには以下の権限が必要でした。

  • Read Metadata(最初から付いています)
  • Read Issue(後述)
  • Read & Write Pull Requests(ラベルを付けるため)

作ったAppを、ChatOpsをやりたいリポジトリにインストールします3
Appがアクセスする範囲を確認し、Acceptします。

ChatOpsをやりたいリポジトリにはSecretも設定します4

  • APP_PEM:生成したPrivate keys(pemファイル)の中身
  • APP_IDGitHub AppのApp ID
ChatOps For Pull Requestsの裏の仕組み

Dockerイメージが実行され、以下のPythonスクリプトが動いています。
actions-chatops/label_app.py at master · machine-learning-apps/actions-chatops · GitHub

この中でIssueを読み取っているところがあり、Read Issue権限が必要でした。

ディスパッチされるワークフロー

コマンドに応じてそれぞれのワークフローを追加します。

name: Workflow of /test-trigger-comment Command
on:
  pull_request:
    types: [labeled]

jobs:
  test-trigger-comment-workflow:
    if: github.actor == 'GitHub App名[bot]'
    runs-on: ubuntu-latest
    steps:
      - run: echo 'hello World'

onで定義する発火イベントをpull requestにラベルが付いたときとしています(ドキュメント)。
これでmachine-learning-apps/actions-chatops@masterからディスパッチされます。

ifによる分岐でgithub.actor(ワークフローを実行したユーザ(ドキュメント)=pull requestにラベルを付けたユーザ)を確認します。
ラベルを付けたのがリポジトリに設定したGitHub Appだった場合に限り、stepsの部分が実行されます。

まとめ

用意するワークフローのyamlファイルは少なくとも2つ

  • コマンドに応じてディスパッチするワークフロー
    • machine-learning-apps/actions-chatops@master Actionを使う
    • GitHub Appを作成する必要あり
  • コマンドに対応して呼び出されるワークフローを1つ以上作る
    • pull requestにラベルが付いたら発火するようにする

f:id:nikkie-ftnext:20200921212900p:plain

感想

GitHub Actionsを使ったMLOpsへの一歩目を踏み出せました!
コマンドをディスパッチしてラベルを付けるという仕組みだったんですねー

動かせたのですが、「GitHubが提供する環境でモデルの訓練をするのはグレーかも」と感じています。
GitHub Actionsには使用制限があり、どんなコンピューティングにでも使えるわけではありません5
変更したスクリプトでモデルを作り、それの性能評価をするMLOps用途が、GitHub Actionsの使用制限に抵触しないと言い切る自信が私にはありません(ご存じの方いたらコメントや @ftnext まで教えてください) 。
紹介したブログでもモデルの作成などは、別の環境6で行っています。
このあたりの線引きがはっきりすれば、GitHub Actionsはデータサイエンスにも一気に広がるかもしれませんね(使用制限が今のままでも、他の環境での実行連携が驚くほど簡単にできれば、広がりそうです)

GitHub Actionsはテンプレートを触るくらいで、体系的に理解できていなかったのですが、Software Design 2020年10月号のGitHub Actions特集に助けられました。

この特集を読んだ今、machine-learning-apps/actions-chatops@masterを改造したり、ラベルを付ける汎用Action labelerリポジトリ)を使うように改造したりできるかもしれないなと思っています。

今回手を動かしたのは、今週金曜日のLTに向けてのネタ探しでもあります。
枠はまだあるようですので、興味ある方はぜひお越しください。
別の実行環境が必要という結論ですが、まだ万策尽きてないです😏


  1. 紹介したブログ記事の大元のリポジトリのワークフローのymlファイルの名前もchatops.ymlでした

  2. IIJさんのブログに詳しかったです:人に紐付かない権限で連携機能を設定できるGithub Apps | IIJ Engineers Blog

  3. インストールしたあとでGitHub Appの権限を変えた場合は、新しい権限を適用するためにConfigurationを操作する必要がありました

  4. Encrypted secrets - GitHub Docs

  5. 「アクションは次の用途には使用しないでください。(中略)gitHubアクションが使用されるリポジトリに関連するソフトウェアプロジェクトの製造、テスト、デプロイ、公開に関連しないその他の行為。」ref: GitHub追加製品の利用規約 - GitHub Docs

  6. k8s上で実行しています(まだまだ先は長いぞ。。) https://youtu.be/Ll50l3fsoYs

akiyokoさんの『現場で使える Django 管理サイトのつくり方』、Django Adminの仕組みやカスタマイズ方法に目から鱗の1冊でした #技術書典

はじめに

頑張れば、何かがあるって、信じてる。nikkieです。
技術書典9が開催中ですね!(9/22(火) 4連休最終日まで)

現場で使えるDjangoの教科書シリーズのakiyokoさんから、新刊『現場で使える Django 管理サイトのつくり方』を献本いただきました。 (ありがとうございます!)
読んだ感想をこのエントリにまとめます。

f:id:nikkie-ftnext:20200919090357j:plain

目次

『現場で使える Django 管理サイトのつくり方』とは

どんな本かはakiyokoさんのブログに詳しいです。

akiyokoさんのDjango本シリーズは毎回楽しみにしており、今回の新刊も購入予定でした。
上のエントリにあるように、まさしく「管理サイトの基本から応用に至るまでの幅広い知識が得られ、Django への理解がさらに深まる」本でした。

前提:nikkieのDjango

管理サイト2は「開発中のテストデータ投入」で主に使ってきました3
カスタマイズはできるらしい4けれど自分が使うことはあまりないだろうとすら思っていました。

読んで:落ちまくった鱗

Django Adminでできることの多さに驚きました。
「開発中のテストデータ投入」だけの利用は宝の持ち腐れでしたね。
例えば、管理サイトでパスワードリセットの機能を有効にしたり、一覧画面をダッシュボードのようにカスタマイズできたりすることを知りました(これは知ったことのごくごく一部です)。

通読して思ったことは、権限(Django Adminで操作できる範囲)を絞った上でカスタマイズすれば、特定のユーザがログインしたあとの画面としても使えそうということです。

例えば、Django Girls Blogは、はてなブログのような登録してブログを書けるWebアプリを目指していると思います。
これを実現する方法の中には、Django Adminを使うという方法もあるのでは?と思いました(経験不足のために筋が悪かったらお知らせください)。
記事を閲覧するにはユーザ登録不要です(Tutorialの実装のままになると思います)。
記事を執筆するユーザには、Postだけを操作できる権限を割り当ててDjango Adminにログインさせます(ログインや執筆機能を一から実装せずに済みます)。

同様に、プロポーザルレビューアプリもレビュアー権限のユーザをDjango Adminにログインさせていたら、一から作るより手短に済んだかもしれません。
プロポーザル投稿には一般ユーザとしてログインを必要とします(Django Girls Blogのような感じで実装する想定です)。
レビューフェーズになったら、特定のユーザにレビュアー権限を割り当てて、カスタマイズしたDjango Adminでレビュー機能を提供します。

まとめると、一部のユーザだけが可能なデータ操作は、カスタマイズしたDjango Adminの利用で実装量を減らせそうという感触を得ました。

また、これまでのシリーズ同様にコラムも充実しています。
便利なパッケージの紹介などなど、コラムからの学びも多々ありました。

Djangoは独学の身(実務では使用経験なし)からすると、akiyokoさんのコードが見られるのも学びが大きいです。
「そんなところに便利なモジュールがあるのか」「テストコードはこんな風に書くのか」などなど、akiyokoさんとペアプロしているような感覚を味わいました。

終わりに

Django Admin、何も知らなかったことを痛感する一冊でした。
仕組みやカスタマイズ方法を知った今、Django Adminで手を動かして、知ったことを知識に変えたい想いに満ちています。
カスタマイズして、Django Girls Blogの記事の一覧画面や編集画面を作ってみるというのは、素振りによさそうですね。
Django Adminのカスタマイズでできることを掴めば、それを使わない場合の実装にも活きてきそうな気がします(クラスの継承を使って設定している部分など)。

今回はインデックス作りとしてざっくり読みましたが、この本はDjango Adminを使った開発の辞書としても使えそうです。
(画面遷移図やクラス変数、メソッド一覧など、akiyokoさんのエントリの紙面サンプルを参照ください)

Djangoチュートリアルをなにか1つ終えていれば、この本から多くを得られると思います。
Django Adminを使いこなし/使い倒したい方はよろしければチェックしてみてください!

P.S. 現場で使えるDjangoの教科書も読み返したら目から鱗でした

ふと基礎編の教科書を読み返し始めたところ、「レビューアプリではこうやればもっとうまくやれていたのか!」と発見ばかりでした。
特に基礎編のビューの章、クラスベースドビューの使い方の指針はなるほどと思いました。

実は教科書は困ったときのインデックスとして使っていて、通読は後回しでした。
Djangoアプリを1つ作り上げた今、akiyokoさんの本を通読して吸収できることは非常に多くありそうです(特にコードまわり)。
なので、現場で使えるDjangoの教科書シリーズは、Djangoについて知り、手を動かした後に読み返して知識を深めるという長く付き合える本なんだと実感しました。

技術書典4から継続してDjangoについて質の高い情報をアウトプットしているakiyokoさんには感服です。
最近はDjangoの入門本が増えてきた印象ですが、akiyokoさんのDjangoの教科書シリーズは現場で使える知識が詰まっていて、私のDjango開発を引き続き支える相棒のような存在になりそうです。


  1. 先日のPyCon JP 2020で共有しています:https://pycon.jp/2020/timetable/?id=203919

  2. 私はTutorialの言葉でDjango Adminと呼ぶことが多いです

  3. 良くも悪くも以下を頑なに守ってきました:ログインページを作ろう(Django admin) · HonKit

  4. たしかモグモグDjangoDjango Admin Cookbook — Django Admin Cookbook 2.0 documentation を知りました