何かを書き留める何か

数学や読んだ本について書く何かです。最近は社会人として生き残りの術を学ぶ日々です。

Curriculum Vitae of XaroCydeykn

Curriculum Vitaeと呼ぶには何かが欠けている気がするが気にしない。

各種アカウント

  • Twitter 何だかんだ学生時代から続けている。ある意味20代の歴史である。
  • Mastodon 流行りだしたので作って放置していたが、一応生きている。
  • misskey.io にわかに流行りだしていたので作ってみた。

Twitterに限らず、複数アカウントを同時に使ったことがないので、どうやって使い分けたらいいのかがわからない。

翻訳

監訳

共著

技術書査読

2016年

2017年

2018年

2019年

2020年

2021年

2022年

2023年

2024年

外部発表

2016年

  • PyConJP で感じる私の成長」PyCon JP 2016 Day1 Lightning Talk

受付後にLTの募集枠が空いていたので深く考えずに登録した。 卒論・修論発表で培った(?)勢い重視の発表で中身の薄さを乗り越えた。 最初のPyCon JPは怖い、という話はある程度共感を得たようである。

2017年

当初は話を聞くだけのつもりであったが、Python 3.6の新機能を調べるうちにメタクラスの部分の置き換えができることに気づいたので発表した。

中身のない概要から如何に内容を絞り出すか、と苦戦した発表。 技術書に書かれていることだけでは難しく、実践を伴わないと意味のある発表にするのが難しい。

  • Respect the Built-in Names」PyCon JP 2017 Day1 Lightning Talk

Reject Conから評判の良かった内容を抽出して膨らませたLT。 意外と琴線に触れる内容だったらしく、アンサーLTまで登場した。

  • 技術書査読・校正の現場から」BPStudy #123 Lightning Talk

間違い探しLT。 筆者も意外といい加減なことを書いているのでそれを検証しつつなんとかするのが査読や校正の役割である。

2018年

  • レガシーDjangoアプリケーションの現代化」DjangoCongress JP 2018 Talk

2017年8月から参画したプロジェクトの経験を元に架空のプロジェクトという見立てを用いて話を作った。

SymPyで学んだことを整理するために発表した。 題材として学部1,2年でやるような数学を選んだつもりだったが、気付いたら整数論も入っていた。

2019年

正しくはQuerySetではなくDjango ORMの失敗談。 PCを持たないのにその場で登壇を決めてしまったので会社の同僚にPCを借りて急ごしらえでスライドを作成した。

ピタゴラス数と無限降下法について勉強して話した。 無限降下法の実装は手探りで証明を理解しつつやったので中々に面白かったのだが、それが発表者に伝わったのかは神のみぞ知る。

2020年

  • 君はcmathを知っているか」PyCon mini Shizuoka 2020 Talk

cmathモジュールの可能性を模索した。 当初の予定はマンデルブロ集合が到達点であったが、冷静に考えてcmathである必要性を感じなかったので、離散Fourier変換と信号処理という電通大の学部3年でやる基本的なテーマを引っ張り出した。

ioモジュールのインメモリーストリーム(StringIo, BytesIO)の実用例を発表した。 BytesIOの中でさらにZipfileを開くという込み入った実装がちょっと気に入っている。

2021年

完全なる一発ネタ。 気軽にPythonを使っていいんだよ、とか気軽にLTやっていいんだよ、というのがテーマであった。 PyCon JP 2021のTwitterでこのLTを元にしたツイートがあって感動した。

  • 組み込み関数powの知られざる進化」PyCon JP 2021 Talk

pow関数に突然追加された機能について数学的な背景を説明した。 数学科でも、情報系でも扱うようなテーマなので、案外みんな知っているのかもしれない。

2022年

  • 残念ながら、1回も登壇せず。書籍作業を優先していたので発表にまで手が回らない状況であった。

2023年

  • 堅牢なPythonコードを書く方法」 BPStudy#189

監訳した『ロバストPython』の概略を説明した。 Pythonの良さと静的型付け言語の良さを良いとこどりして文字通り良いコードを書いていきたい。 「型が形無し」は邦訳にあるジョークだが、それに合わせたか「継承に警鐘を鳴らす」という言い回しが咄嗟に出た。

役に立たないオブジェクトを作る発表。 英語資料を作るのはよいのだが、英語発表は非常に大変だった。

2024年に読んだ本

雑な読書記録

買っても読まず、読んでも特に記録を残さずに思い出に残らないので、年単位で読んだ本と簡単な感想を残しておくことにしよう。 いつも、書評を書こうと思い立つもすぐに断念してしまうので「簡単な感想」にとどめてそのハードルを下げるのが目的である、と言っておきながら5年目である。 過去のリストは以下の通り。

自分が読み返して「こんなの読んだのか」と感慨に耽るのが目的なので、気楽に読み流してほしい。

犯罪学教室のかなえ先生 『世の中の8割はどうでもいい。』

www.shogakukan.co.jp

人生をバランスよく生きてくための「テキトー術」を、『人生がクソゲーだと思ったら読む本』で話題を呼んだ自称・日本一テキトーなVTuberが説きます!

大学の同級生が編集を担当した、ということで購入。 筆者はVTuverで、学生時代や法務教官時代の経験を元に「テキトー」に生きる方法を描いたエッセイ。 8割はどうでもいいとかテキトーという言葉から投げやりな生き方をなんとなく想起してしまうがそうではなく、自分でどうしようもない部分に悩む必要はない、というのは面白い視点であった。 語りかける文体ではありつつも読みやすいというも面白い。語りかける文体は得てして読みづらい代物になるのだが、読みやすかった。 エッセイという体歳上、個人の経験がベースであり、何らかの論文や研究がベースになっている主張ではないことは一応気にしておいてもいいかもしれない。 なお、肩書に「日本初の元国家公務員の男性VTuber」とあるが、あまくだり氏の方が早い気がする。 もっとも、現在のあまくだり氏はVTuberではなくYouTuberかつカードショップオーナーである。

マウンティングポリス『人生が整うマウンティング大全』(技術評論社

gihyo.jp

人間関係あるところにマウントあり,マウンティングを制する者こそが人生を制する。

全体の三分の二はカタログ的にマウンティングの事例を集めたもの、残りは人生に基づく人生訓、といった構成である。 人はマウントしたがるものである、自分に秘めるマウント欲も否定せず、相手のマウント欲を尊重しつつコミュニケーションするとうまくいきますよ、というのが主題だろうか。 とはいえ、かなり穿ちすぎな本である。 「マウンティング枕詞」も、無意識にマウントする人には有効かもしれないが、意識して、つまりメタメッセージにマウントを織り込んで話す人々には逆にそれを見透かされる気がしてならない。 「マウンティングエクスペリエンス(MX)」の考察も雑で楽しい読み物の域を超えない。

もっとも、こういう書評めいた感想を書く行為こそ筆者からすればマウンティングである、と言われるのだろう。

岡奈津子『新版 〈賄賂〉のある暮らし』(白水社

www.hakusuisha.co.jp

ソ連崩壊後、独立して計画経済から市場経済に移行したカザフスタン。国のありかたや人びとの生活はどのような変化を遂げたのか。

市場経済化したカザフスタンの生活実態に迫った研究書。 JETROの『アジ研ワールド・トレンド』の記事をまとめ、一般の人にも読みやすくしたものである。 〈賄賂〉と括弧つきなのは、賄賂とお礼の区別が厳密に定義できないという故である。

カザフスタンという国をあまり知らなくても、そこまで興味が無くてもものすごく面白く読める本である。 何をするにも賄賂、賄賂と日本では考えにくい状況が展開される。 賄賂が横行するのは給与が低いせいだ、という言説もカザフスタンの実態の前には不十分である。 まず、仕事を得るにも賄賂が必要であり、しかもその職を維持するにも上司に上納金を送るなど、構造と賄賂が一体化しているため、単に給与を上げても解決しない。 また、仕事を得る際に賄賂を払っても、市民からわいろを受け取ればペイできる、つまりある種の投資でもある、という主張にもびっくりした。 なお、新版で追加された解説によるとカザフスタン全国民が賄賂を使っているわけではなく、旧ソ連の国々と比較するとそこまでひどい国ではないらしい。

一度絶版になった本だが、やはりちゃんとした本は復刊するのである。 手に入りやすくなったので、カザフスタン中央アジアにそこまで興味が無くても読んでみてほしい。

『データエンジニアリングの基礎』の査読を担当しました

データエンジニアリングとはこれ

2024年3月27日にオライリージャパンから『Fundamentals of Data Engineering』の邦訳である『データエンジニアリングの基礎』が発売される。

www.oreilly.co.jp

本書は「データエンジニアリングライフサイクル」という概念を軸にデータシステムの要件を整理、システムの構築、データエンジニアの立ち位置などを詳細に論じたものである。 データサイエンティストではなくデータ分析の基盤を作るデータエンジニアの本ではあるものの、普段はWebシステムを作っているのでそこまでデータサイエンス関係ないんだよな、という人でも面白く読めるはずである。 特定のツールに依存しない説明であり、結果的にRDSしか使わないという選択をしたとしても本来はここまで考えないといけないのか、という記述の連続である。

データエンジニアに限らず、何らかのソフトウェアシステムに携わる人ならば是非とも読んでみてほしい。

Pythonの io.BytesIO と zipfile.ZipFile の組み合わせ

with文がやってくれるのはどこまでなのか

インターネット経由で取得したZIPファイルを手元で加工する、という状況を考える。 たとえば、次のようなコードを書いたとする。 URL は ZIPファイルを取得できるものならば何でもよいが、今回は環境に配慮してローカルに1回だけダウンロードしてhttp.serverで簡易Webサーバを立てることでキャッシュしている。

import io
import urllib.request
import zipfile

URL = "http://localhost:8000/python-3.12.2-embed-amd64.zip"

with urllib.request.urlopen(URL) as f:
    content: bytes = f.read()

with zipfile.ZipFile(io.BytesIO(content)) as zf:
    names: list[str] = zf.namelist()

for name in names:
    print(name)

urllib.request.urlopen() 経由で取得したものはbytesであり、ファイルオブジェクトではない。 zipfile.ZipFile の第一引数file はファイルのパス、ファイルオブジェクト、 pathlib.Path のいずれかである必要があるため、urllib.request.urlopen() 経由で取得したものをそのまま渡すわけにはいかない。 そのため、 io.Bytes() を経由することでインメモリーストリームとして渡す。

このエントリの主題は、上記のコードにおける io.BytesIO(content) の取り扱いについてである。 with zipfile.ZipFile(io.BytesIO(content)) as zf: と記述しているので、 names: list[str] = zf.namelist() が完了したら zipfile.ZipFile(io.BytesIO(content))close() メソッドが呼ばれる。 その際に、中身である io.BytesIO(content) は閉じられるのだろうか。

公式ドキュメントとソースコードを巡る冒険

公式ドキュメントには

The buffer is discarded when the close() method is called.

とある通り、 BytesIOclose() メソッドが呼ばれなければインメモリーストリームのバッファは解放されない。

まず、 ソースコード にある __init__() から説明に必要な個所を抜き出す。

class ZipFile:
    fp = None                   # Set here since __del__ checks it

    def __init__(self, file, mode="r", compression=ZIP_STORED, allowZip64=True,
                 compresslevel=None, *, strict_timestamps=True, metadata_encoding=None):
        """Open the ZIP file with mode read 'r', write 'w', exclusive create 'x',
        or append 'a'."""
        ...

        # Check if we were passed a file-like object
        if isinstance(file, os.PathLike):
            file = os.fspath(file)
        if isinstance(file, str):
            # No, it's a filename
           ...
        else:
            self._filePassed = 1
            self.fp = file
            self.filename = getattr(file, 'name', None)
        self._fileRefCnt = 1
        ...

ここで認識してほしいのは以下の3点である。

  • zipfile.ZipFile の第一引数にファイルオブジェクトを渡すと self._filePassed = 1 となる。この変数は __init__()でのみ定義され、変更されない。
  • zipfile.ZipFile の第一引数にファイルオブジェクトを渡すと self.fp = file 、つまり self.fpBytesIOインスタンスが入る。
  • zipfile.ZipFile の第一引数にファイルオブジェクトを渡すと self._fileRefCnt = 1 となる。今回のスニペットだと self._fileRefCnt = 1 は1のままである。

次に ソースコード にある close() メソッドを確認する。

    def close(self):
        """Close the file, and for mode 'w', 'x' and 'a' write the ending
        records."""
        if self.fp is None:
            return
        try:
            ...
        finally:
            fp = self.fp
            self.fp = None
            self._fpclose(fp)

ここでわかることは以下の点である。

  • 内部変数fpself.fp 、つまり BytesIOインスタンスが入る。
  • その後、 self.fpNone となる。
  • self._fpclose(fp) が実行される。

そして、 ソースコード にある _fpclose()メソッドを確認する。

    def _fpclose(self, fp):
        assert self._fileRefCnt > 0
        self._fileRefCnt -= 1
        if not self._fileRefCnt and not self._filePassed:
            fp.close()
  • self._fileRefCnt = 1 なので、if 文に到達する際に `self._fileRefCnt == 0 となり、条件式の左辺は True である。
  • self._filePassed = 1 なので、条件式の右辺は False である。
  • よって、 if not self._fileRefCnt and not self._filePassed:False となり、 fp.close() は実行されない。

以上より、「 io.BytesIO(content) は閉じられるのだろうか」という疑問は「閉じられません」となる。

では、閉じるにはどうすればよいのか、自分で明示的に閉じるしかない。

with io.BytesIO(content) as bs:
    with zipfile.ZipFile(bs) as zf:
        names: list[str] = zf.namelist()

こうすれば、 names: list[str] = zf.namelist() が実行された後、zipfile.ZipFileが閉じられ、 io.BytesIO が閉じられる。 これで安心して眠りにつくことができる。

ガベージコレクションの存在

しかし、このスニペット程度ならば、ガベージコレクション先生が気を利かしてくれるはずだ。

import io
import gc
import urllib.request
import zipfile

gc.enable()

URL = "http://localhost:8000/python-3.12.2-embed-amd64.zip"

with urllib.request.urlopen(URL) as f:
    content: bytes = f.read()

data = io.BytesIO(content)
with zipfile.ZipFile(data) as zf:
    names: list[str] = zf.namelist()

for name in names:
    print(name)

print(gc.is_tracked(data))
...
True

当然、 io.BytesIO(content) はガベージコレクタによって捕捉されており、必要に応じて回収されるだろう。

今後の課題

今回のスニペット程度ではガベージコレクタによって回収されるので、閉じ忘れても特に支障はないと思われる。 「ZIPファイルを手元で加工する」の内容如何では、ガベージコレクションによって回収されない状況が起き得るか、が自分の課題である。 また、 gc モジュールの使い方がイマイチわかっていないので、それの調査も課題である。