何かを書き留める何か

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

PythonicではなくPythonesqueなクラスを書く

It’s…

プログラミング言語Pythonにおいて、Pythonらしい、といった概念をPythonicと呼ぶことがある。 Pythonicらしい書き方を知るにはZen of Pythonや『Effective Python』の第一章を参照すればその一端を知ることができる。

Effective Python ―Pythonプログラムを改良する59項目

Effective Python ―Pythonプログラムを改良する59項目

一方で、Pythonesqueという言葉もある。Pythonesqueとはプログラミング言語Pythonの名称のもとになったイギリスのコメディグループMonty Pythonのスケッチの不条理さを表した造語である。 折角なので「チーズショップ」(第3シーズン第7話)スケッチを引用する。

youtu.be

普段はPythonicな書き方を目指すべきであるが、たまにはPythonesqueな書き方をしてもよいのではないだろうか。 今回は、Pythonの特殊メソッドを変な風に実装することでPythonesqueなクラスを書く。 先ほど引用した「チーズショップ」スケッチのような辞書クラスを定義してみよう。

import collections


class CheeseShop(collections.UserDict):
    def __len__(self):
        return 0

    def __getitem__(self, item):
        raise KeyError


if __name__ == "__main__":
    shop = CheeseShop(Caerphilly="here")
    print("len(shop) = {}".format(len(shop)))
    for key in ("Red Leicester", "Tilsit", "Caerphilly"):
        try:
            print(key, shop[key])
        except KeyError:
            print("{} is not here!".format(key))

出力結果は以下の通りになる。

len(shop) = 0
Red Leicester is not here!
Tilsit is not here!
Caerphilly is not here!

絶対にこのような実装をしてはいけない(普通は良心が否定するか、レビュワーに殴られる)。 しかし、Python言語を理解する上では中々面白いと思う。

Pythonでバイナリ列から画像の種類を判別する

拡張子を信じたい人生であった。

画像ファイルがネットワークを介して飛んでくる。 拡張子を信じて保存をしたいが、拡張子だけ書き換えたり中身が改ざんされているかもしれない。 画像フォーマットごとにあるヘッダを読んで画像を識別したい。

表題には「Pythonで」と書いたが、本質的にバイナリ列と正規表現が扱えるプログラミング言語ならば大差は無いと思われる。 意外とPythonで書かれた記事が見当たらないので一応まとめておこうという気になって書いたのがこのエントリ公開の動機である。

基本方針

大抵の画像フォーマットには仕様が定められている。 つまり、何らかの決まったヘッダーが存在する。 そのヘッダー情報を正規表現で読み取って判別しよう、というのが基本方針である。

JPEG

JPEGは離散コサイン変換を利用した非可逆圧縮の画像フォーマットである。 ISO/IEC 10918-1で仕様が定められ、JISでもJIS X 4301で定まっている。 仕様を読み解くと*1、画像の始まりを示すマーカー(SOI)として\xff\xd8が定義されている。 よって、JPEGがどうかを判定するには次のようにすればよい。

import re
import typing


def is_jpg(b: bytes) -> bool:
    """バイナリの先頭部分からJPEGファイルかどうかを判定する。"""
    return bool(re.match(b"^\xff\xd8", b[:2]))

PNG

PNG可逆圧縮の画像フォーマットである。 ISO/IEC 15948で仕様が定められ、PNGファイルの最初の8バイトは\x89\x50\x4e\x47\x0d\x0a\x1a\x0aであると定められている*2。 よって、PNGがどうかを判定するには次のようにすればよい。

import re
import typing


def is_png(b: bytes) -> bool:
    """バイナリの先頭部分からPNGファイルかどうかを判定する。"""
    return bool(re.match(b"^\x89\x50\x4e\x47\x0d\x0a\x1a\x0a", b[:8]))

GIF

GIFは可逆圧縮の画像フォーマットである。 圧縮アルゴリズムのライセンスをめぐるゴタゴタがあったそうであるが私はその歴史について全く知らない。 私にとってGIFは画像ファイルというよりアニメーションのためのフォーマットである。

https://www.w3.org/Graphics/GIF/spec-gif89a.txtにあるGIFの仕様を眺めると、ヘッダーとして最初はGIF、つまり\x47\x49\x46となり、続いてバージョン番号である87a89aが続く。 よって、GIF8がヘッダにあるかどうかを調べればよい*3

import re
import typing


def is_gif(b: bytes) -> bool:
    """バイナリの先頭部分からGIFファイルかどうかを判定する。"""
    return bool(re.match(b"^\x47\x49\x46\x38", b[:4]))

PDF

PDFはAdobe社が開発したドキュメントフォーマットであるが、画像として送り付けられる場合もある。 ヘッダは%PDF-なので、それを調べればよい*4

import re
import typing


def is_pdf(b: bytes) -> bool:
    """バイナリの先頭部分からPDFファイルかどうかを判定する。"""
    return bool(re.match(b"^%PDF", b[:4]))

Windows Bitmap

Windows BitmapはIBMMicrosoftが開発した画像フォーマットである。 ここ最近、見かけた覚えがないがネットワークの向こう側にいる人間にとっては身近な画像フォーマットかもしれないのだ。 冒頭がBMつまり\x42\x4dから始まるのでそれをチェックすればよい。

import re
import typing


def is_bmp(b: bytes) -> bool:
    """バイナリの先頭部分からWindows Bitmapファイルかどうかを判定する。"""
    return bool(re.match(b"^\x42\x4d", b[:2]))

拡張子を信じることができる世界ならば

今までの前提の否定、つまり拡張子と実際のデータが一致している場合は既存のライブラリで間に合う。 それはmimetypesモジュールである。

19.5. mimetypes — ファイル名を MIME 型へマップする — Python 3.6.1 ドキュメント

最後に

よくあるフォーマットをまとめて、バイナリを入れるといい感じにContent-Typeを返してくれるライブラリが欲しいなあとか思ったり思わなかったりする。

*1:実際は「jpeg ヘッダ」で検索したのち、仕様を調べてで裏取りを行った。

*2:最初はWikipediaで知った

*3:これも最初に知ったのはWikipedia

*4:Vimで適当なPDFファイルを開いてバイナリを調べたのち、仕様を調べた。

『モンティ・パイソンができるまで ジョン・クリーズ自伝』を読んだ。

モンティ・パイソンができるまで ジョン・クリーズ自伝』を読んだ。

www.kinokuniya.co.jp

ジョン・クリーズはイギリスの著名なコメディアンでモンティ・パイソンのメンバーの一人。 幼少期からモンティ・パイソンにおける最初の撮影(空飛ぶ羊のスケッチ)までの訳者曰く「四半生記」。 クリーズの多才さが存分にわかる。

原著のオーディオブックも販売されている。 これは盟友グレアム・チャップマンに倣ったのであろうか。 死後にアニメ化されるかもしれない。

Amazon.co.jp: So, Anyway...: The Autobiography: John Cleese: 洋書

グレアム・チャップマンの自伝

A Liar's Autobiography : Graham Chapman, David Sherlock, Alex Martin, David Yallop, Douglas Adams : 洋書 : Amazon.co.jp

グレアム・チャップマンの自伝を元にしたアニメーション Amazon CAPTCHA