らっちゃいブログ

日々の学びと気づきを発信するブログ

【Python】Pillowを使ってサムネイルを生成してみよう

サムネイルといえば ImageMagick ですが、自由にライブラリをインストールできない環境ではアプリケーション内でサムネイルを生成する必要がありますね。

Python では Pillow というライブラリを使ってサムネイルを生成することができますので、本記事では簡単な実装例を紹介してみます。

なお、本記事では Python2.7 ベースで記載することにします。(バイナリを扱う処理は Python3 よりPython2.7の方がわかりにくい気がするので)

インストール

pip を使ってインストールするだけです。

$ pip install pillow

適用にサムネイルを作成してみる

以下は 100x100 のサムネイルを生成するコードです。

from PIL import Image

img = Image.open('original.jpg', 'r')
thumbnail_size = (100, 100)
img.thumbnail(thumbnail_size)
img.save('thumbnail.jpg', 'JPEG')

いかがでしょうか?特に内容を解説しなくても、ぱっと見てなんとなく何をしているかわかりますね。シンプルなサムネイルを作る分には、これだけ知っておけば問題ないです。

ちなみに import 文が PIL となっていますが、これは Pillow が PIL プロジェクトの fork であるためです。PIL 自体の開発は止まっていますので、気にせず Pillow を使うようにしてください。

メモリ上でサムネイルを作成する

さきほどのコードはファイルシステムに保存された画像ファイル名を指定して、そのままファイルシステムへサムネイルを書き出す方式でした。

大体の場合はそれで十分なのですが、場合によってはファイルシステム一切経由せずネットワークのみで完結したいケースもありますので、その方法についても書いておきます。

以下は S3 から読み込んだ画像ファイルをサムネイル化してS3 にそのままアップロードすることを想定したコードになります。

from PIL import Image
from StringIO import StringIO
from io import BytesIO

# S3から画像ファイルを読み込む

img_bytes = fetch_image_from_s3()

img = Image.open(StringIO(img_bytes))
thumbnail_size = (100, 100)
img.thumbnail(thumbnail_size)
thumbnail = BytesIO()
img.save(thumbnail, 'JPEG')

# サムネイルを S3 へアップロードする
upload_thumbnail_to_s3(thumbnail.getvalue())

さきほどとは違い、img オブジェクトはバイトデータを使って取得しています。また、サムネイルの生成についても img.save には書き出し先として BytesIO を渡すようになりました。注意してほしいのが、この方式はメモリへの負担が大きいですので、サイズの大きい画像ファイルを扱う場合には避けるようにしましょう。

exif 情報を考慮してサムネイルを生成する

JPEG 画像を扱う場合の話ですが、前述のサムネイル処理では exif(Exchangeable digicame-exif file format)情報を反映してくれません。

そのため、オリジナル画像が90度回転しているものをサムネイル化すると、横向きのサムネイル画像が作成されてしまいます。(ウェブサービスでたまに横向きのサムネイルを見かけると思いますが、そのほとんどは exif 情報が反映されていないのが原因です)

そういったことにならないよう、サムネイル作成時にオリジナル画像と同じ向きになるように回転させてやる必要があります。 つまりは、ImageMagick でいう --auto-orient オプションと同じ処理を自前で用意しなければいけません。

自前で用意と聞くとめんどくさそうな印象を受けますが、ご安心ください。そんなに難しい処理ではありません。

まずは JPEG 画像から exif 情報を抽出します。 exif 情報の抽出には、Image オブジェクトの _getexifを使います。

img = Image.open('original.jpg', 'r')
exif = img._getexif()

次は回転方法を決定するために Orientation 情報を取得します。 exif 情報は辞書となっており、Orientation値 は 0x112 をキーとして取得することができます。

# exif が Orientation値を持っていないこともあるので、1 (回転なし) をデフォルトとして取得するようにします
orientation = exif.get(0x112, 1)

Orientation 値を得ることができたら、あとは決められた処理で画像を回転させてあげるだけです。

以下は Orientation 値に対応する回転関数を定義したものです。なお、回転関数については、こちらの記事を参考にさせていただきました。

convert_image = {
    # そのまま
    1: lambda img: img,
    # 左右反転
    2: lambda img: img.transpose(Image.FLIP_LEFT_RIGHT),
    # 180度回転
    3: lambda img: img.transpose(Image.ROTATE_180),
    # 上下反転
    4: lambda img: img.transpose(Image.FLIP_TOP_BOTTOM),
    # 左右反転&反時計回りに90度回転
    5: lambda img: img.transpose(Image.FLIP_LEFT_RIGHT).transpose(Image.ROTATE_90),
    # 反時計回りに270度回転
    6: lambda img: img.transpose(Image.ROTATE_270),
    # 左右反転&反時計回りに270度回転
    7: lambda img: img.transpose(Image.FLIP_LEFT_RIGHT).transpose(Image.ROTATE_270), 
    # 反時計回りに90度回転
    8: lambda img: img.transpose(Image.ROTATE_90),
}
thumbnail_img = convert_image[orientation](img)

やるべきこととしては以上です。あとは回転した画像をサムネイル化してやればOKですね。

では、最終的に必要となるコードを以下にまとめてみます。

img = Image.open('original.jpg', 'r')
exif = img._getexif()
orientation = exif.get(0x112, 1)
convert_image = {
    # そのまま
    1: lambda img: img,
    # 左右反転
    2: lambda img: img.transpose(Image.FLIP_LEFT_RIGHT),
    # 180度回転
    3: lambda img: img.transpose(Image.ROTATE_180),
    # 上下反転
    4: lambda img: img.transpose(Image.FLIP_TOP_BOTTOM),
    # 左右反転&反時計回りに90度回転
    5: lambda img: img.transpose(Image.FLIP_LEFT_RIGHT).transpose(Image.ROTATE_90),
    # 反時計回りに270度回転
    6: lambda img: img.transpose(Image.ROTATE_270),
    # 左右反転&反時計回りに270度回転
    7: lambda img: img.transpose(Image.FLIP_LEFT_RIGHT).transpose(Image.ROTATE_270), 
    # 反時計回りに90度回転
    8: lambda img: img.transpose(Image.ROTATE_90),
}
thumbnail_img = convert_image[orientation](img)
thumbnail_size = (100, 100)
thumbnail_img.thumbnail(thumbnail_size)
thumbnail_img.save('thumbnail.jpg', 'JPEG')

まとめ

いかがでしたでしょうか。ImageMagick ほど楽はできませんが、ある程度なら少ない実装量で実現できることがわかりましたね!

ぜひお試しください。

【祝400ブクマ達成】ホッテントリ入りする喜びと哀しみについて語ってみる【初バズ】

経緯

こちらの記事で初のホッテントリ入りを果たしました!めでたい。

racchai.hatenablog.com

普段だと土日のアクセス数なんて50もいけば良い方なのですが、何気なくアクセス数を確認してみたら3000アクセスを超えているわけです。

人間、本当に驚くと固まるんですね。頭が真っ白になって、何が起きているのかなかなか把握できなかったです。リアルタイムで50-60人ほどのアクセスがずっと続いていることを確認して、ようやくバズっていることを理解しました。

ブログを開設してもうすぐ2ヶ月になりますが、初バズだったので非常に嬉しいです!バズるという現象は、ブログの更新を続けていればいつかは来るものとされていますが、人によっては一年以上かかるという記事を見かけたこともあり、自分はいったい何年かかるかなあと不安に思っていたところでした。

それにしても、公開から5日も経過した記事がなぜここまでバズる結果になったのか。。自分には珍しくささっと書き終えた記事だったこともあり、驚きを隠せません。

喜び

バズったこと自体もうれしいですが、それ以上に大量にブクマコメントをいただけたことが私にとって一番の『喜び』でした。

ブログを書いている人ならわかると思いますが、誰にも読まれていないと感じる状態が一番つらいんですよね。

初めはアクセス数が増えてくるだけでもうれしかったりしました。でも、それはただの数字でしかないわけなので、読まれたい欲を満たすものではないのです。

今回コメントいただいた中には、記事の内容を評価してくださっている方もいて、感激しています。これでまた、当分ブログを続けることが出来そうです。本当にありがとうございます!

哀しみ

ホッテントリ入りはうれしいことばかりではないというのが、今回の発見でした。

一般的には、ネガティブなコメントが付くのが悲しいとか、炎上するのがつらいとかがありますが、私の場合はそれではありません。

二日間ほど総合とテクノロジーカテゴリの上位に掲載されていたものが、突然消え去ったのです。ホッテントリから外れた瞬間、ブログへのアクセスはみるみる落ちていき、もうすでにいつも通りのアクセス数に戻りつつあります。

盛大に盛り上がった後、突如それがなくなってしまったので、心にぽっかり穴が開いたような気持ちです。この喪失感はこれまで経験がありません。

遊びに来ていた孫が一泊で帰ってしまったみたいな気持ちなんでしょうか。孫いないからわかりませんが。

ここ数日は、帰ってしまった孫を想いながら思い出の写真を見るように、6/4 のランキング2位になったスクリーンショットを眺めて懐かしんでおります。

f:id:racchai:20160607081331p:plain

ひとこと

ホッテントリから消えるのはいいですが、一応400ブックマークも付いた記事なんですから、もう少し優しくフェードアウトさせてくれてもいいじゃないですか、はてなさん。

tailコマンドで対象ファイルがローテートされても自動で追随する方法

地味に便利だったのでメモしておきます。

ログファイルをリアルタイムで確認するときに利用する tail -f コマンドですが、ファイルサイズでローテートされるようなログだとすぐに止まってしまい、改めてコマンドを実行し直す必要があり、ストレスが溜まりますよね。

tail のオプションにはぜひ -F を与えてあげてください。たったそれだけで、リネームされたりローテートされても自動的に追随してくれるようになります。

本当でしょうか?tail の man を見てみましょう。

-F   The -F option implies the -f option, but tail will also check to see if the file being followed has been renamed or rotated.  The file is closed and reopened
     when tail detects that the filename being read from has a new inode number.  The -F option is ignored if reading from standard input rather than a file.

つまり、リネームやローテートが起きたら新しいinode番号を検知してオープンし直してくれるようです。

では試してみます。まずは -f オプションの挙動から見てみましょう。

以下のように tail.txt を作成し、tail -f しておきます。

$ echo "a" > tail.txt
$ tail -f tail.txt
a

この状態で tail.txt を tail2.txt にリネームし、tail.txt へ"b"を出力します。つまりローテートを発生させます。

$ mv tail.txt tail2.txt && echo "b" >> tail.txt

結果はこちら。

$ tail -f tail.txt
a

変化なしです。-f ではローテートに追随してくれませんでした。

それでは-Fの動きを確認してみましょう。

$ echo "a" > tail.txt
$ tail -F tail.txt
a

準備ができたところで、ローテートしてみます。

$ mv tail.txt tail2.txt && echo "b" >> tail.txt

結果はどうなっているかというと

$ tail -F tail.txt 
a
b

すばらしい。しっかりローテートに追随してくれていますね。

いまのところ -F オプションを使うことによる弊害はなさそうなので、今後は -f ではなく -F を使っていこうと思います。

curlに名前解決を操作できるオプションがあって超絶便利だった話

小ネタですが、初めて知って感動したので共有させてください。

例えば

  • ローカルサーバに本番のドメインでHTTPアクセスしたい
  • DNS を変更する前に変更後のドメインでHTTPアクセスしたい

のようなケースですが、通常 hosts ファイルを編集して解決することが多いと思います。

今回はなんと、curl を使って確認する場合に限り、いちいち hosts を編集しなくても名前解決を操作することができるオプションを見つけてしまいました。こちらです。

--resolve <host:port:address>

すごい解決してくれそうなオプション名ですね!

使い方はほとんど見たままなのですが、名前解決したいドメイン&ポートと、解決結果として使用するIPを指定するだけです。

例えば www.google.com へのアクセスをローカルアドレスに向ける場合は、以下のように実行します。

$ curl -v --resolve www.google.com:80:127.0.0.1 http://www.google.com/
* Added www.google.com:80:127.0.0.1 to DNS cache
* Hostname was found in DNS cache
*   Trying 127.0.0.1...
* Connected to www.google.com (127.0.0.1) port 80 (#0)
> GET / HTTP/1.1
> User-Agent: curl/7.35.0
> Host: www.google.com
> Accept: */*
> 
< HTTP/1.1 200 OK
* Server nginx is not blacklisted
< Server: nginx
< Date: Thu, 02 Jun 2016 08:37:29 GMT
< Content-Type: text/html
< Content-Length: 612
< Last-Modified: Tue, 04 Mar 2014 11:46:45 GMT
< Connection: keep-alive
< ETag: "5315bd25-264"
< Accept-Ranges: bytes
< 
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>
* Connection #0 to host www.google.com left intact

無事ローカルに立てている Nginx のウェルカム画面を取得することができました。

実際の名前解決ですが、こちらを見るとちゃんと127.0.0.1として解決してくれていることがわかります。

* Added www.google.com:80:127.0.0.1 to DNS cache
* Hostname was found in DNS cache
*   Trying 127.0.0.1...

もちろん、Host ヘッダーは www.google.com が指定されます。

> Host: www.google.com

これでDNS変更前のAPI試験が捗るぜ!

魚の旨い店で三食丼を食べてきた

毎週恒例となりました食べてきたシリーズです。

今回のお店はこちら。魚の旨い店です。

いやいや、魚が旨いかはどうでもいいから店名を書けよって思われそうですが、これが店名なんです。なんとも紛らわしい。

それでは入店します。まずはレジで先に注文とお支払いを済ませる形式でした。レジ前のメニューを見ながら悩む。

焼き魚の定食いいなあ。さばの塩焼き大好きなんですよね。あれ、肉系の定食もあるのか。魚の旨い店なのに。そう、ここは魚の旨い店なんだから、新鮮な刺身が食べれるに違いない。

ということで三色丼に決定。

追加料金を払えばご飯の量を増やすこともできたのですが、具が増えないとバランスが悪くなりそうなので今回は普通盛りにしました。

ちなみに、いま思えばまぐろまぐろまぐろ丼にしておけばよかったと若干後悔してます。名物らしいので。今回、文字が小さくて名物だと気付けなかったので、もしお店の関係者が見てくれてたらぜひ大きくしてほしいです。

注文が終わって席で冷たいお茶を飲んでいると、ほんの数分で三色丼が到着しました。

見た目はなかなかですね。玉子、大葉、わさびあたりでご飯が見えないように隠してる感は否めませんが、美味しそうです。

三色の内訳は

  • ネギトロ
  • カンパチ
  • サーモン

でした。

それではいただきます。

なるほど、お刺身はどれも新鮮で美味しいですね。ネギトロも安っぽくない、ちゃんとしたネギトロでした。さすが魚の旨い店。

心配していたご飯の量も、結果的にはちょうどよかったです。丼ぶりが深くて、ぎりぎりまでご飯が盛られていたので結構な量でした。大盛りにしてたら食べきれなかったかも。

値段もそんなに高くないですし、味も満足だったので、ぜひまた来たいですね。

ちなみにこちらのお店、夜は日本酒の飲み放題コースがあるみたいです。4500円でコース料理付きということで、この内容にしてはお安く設定されています。銘柄を見る限りだとなかなかのラインナップですし、夜に来るのも悪くないお店ですね。

というところで、今回のレポートはこれで終了です。また来週!

DjangoでDBのフィールドに選択肢を設定する

概要

やりたいこととしては、特定のフィールドに指定できる値をEnum化するようなイメージです。

選択肢リストを設定する

フィールドに choices を指定することで選択肢を設定することができます。

choices の中身ですが『2要素のタプルからなる iterable(リストまたはタプル)』と定義されています。

つまりはこんな感じ。

GENDER_CHOICES = (
    (1, 'Male'),
    (2, 'Female'),
)
class Person(models.Model):
    gender = models.IntegerField(choices=GENDER_CHOICES)

見てわかる通り、一つ目の要素がDBに格納する値で二つ目が表示名です。

選択肢を利用するメリット/デメリット

メリットとしては、そのフィールドに格納され得るデータがプログラム上で定義できるため、追加変更が楽です。

また、以下のようにして設定値に対応する表示名を得ることができます。個人的にはこれがやりたくて使ってるところもあります。

person = Person(gender=1)
person.get_gender_display()
'Male'

デメリットですが、定義した選択肢の値が格納されることを強制できません。つまり設定した選択肢以外の値も保存され得ます。ですので、自分で入力チェックなりビジネスロジックを書いて意図しない値が入らないことを保証する必要があります。

MySQLENUM型でいいじゃん

という方もいそうなので、この話題にも言及しておきましょう。

ENUMを実際に使うとこんな感じでしょうか。

CREATE TABLE person (
  gender ENUM('Male', 'Female')
);

MySQLENUM型を使っても良いのですが、選択肢を増減するためにテーブル定義を変更する必要が出てくるため、運用を考えると使いたくないのです。また、DBに格納される値と同じEnum定数をプログラム上にも定義するケースが多く、定数の二重管理になりやすい点も無視できませんね。

ちなみにENUM型はSQLアンチパターン本でも扱われていますので、世間的にもよろしくないものと認知されていると思います。

SQLアンチパターン

SQLアンチパターン

SQLアンチパターンによると、ENUM型を使ってよいとされているのは、ON/OFFのように変更されることのない相互排他的な値を扱う場合だけとのことです。

今回例として挙げた性別はこれに該当するでしょうか?

いいえ、該当しません。依頼人の要望で『内緒』を保存したいということもあり得ます。もはや、ENUMは封印してしまうべきでしょう。

定数を管理するテーブルを作ればいいじゃん

ENUM を使わない場合は、以下のように参照テーブルを作る方法が一般的ですね。Django 以外を使うときは私もそうします。

CREATE TABLE gender (
  id VARCHAR(20),
  name VARCHAR(20)
);
INSERT INTO gender (key, name) VALUES (1, '男性'), (2, '女性');
 
CREATE TABLE person (
  gender_id INT(11),
  FOREIGN KEY (gender_id) REFERENCES gender(id) ON UPDATE CASCADE
);

確かにこれならやりたいことは実現できます。しかし、いつも思うのですが、表示名を取得するの面倒じゃないですか?わざわざ gender テーブルをジョインして name フィールドを引っ張ってくる必要がありますよね。それを避けるためには、結局プログラム上にIDと表示名を管理する Enum を定義することになり、定数の二重管理に逆戻りです。

結局、どうやってもプログラム上に Enum を定義することになるのですから、それだけで全部管理できるようにするのがベストだとは思いませんか?

本記事にて『入力チェックなりビジネスロジックを書いて意図しない値が入らないことを保証する必要がある』というデメリットをお伝えしましたが、そんなに難しいことではありませんよね。選択肢にない値を保存しようとしたらエラーにするだけで良いのですから、一か所に簡単なロジックを書くだけで済みます。

結論

ENUM型を使うのはやめてフィールドに選択肢を設定しよう!

子供が産まれて激変した日常を語ってみる

6:00

まず朝が早い。6時には子供に叩き起こされる。ここ10年は8時近くに起きる生活を続けていたため、非常につらい。とはいえ、寝起きの子供はご機嫌でマジ天使なので10分ほどいちゃつく。

6:10

朝起きたらまずやること。それは子供の体温を測ることだ。熱があったら保育園に預けられないし、病院に連れていかなければならない。

6:20

体温を測ったら子供にごはんを食べさせる。朝食は私より早起きしている妻が作ってくれている。朝食を食べさせるのには平均して30分はかかる。地味に大変な作業だ。最近は自分が食べる時間がないので、食べさせながらコップ一杯の野菜ジュースを飲んで済ませることが多い。ご飯をゆっくり食べれない、またはそもそも食べる時間がないというのは育児あるあるだろう。

6:50

朝食が終わったら、オムツ替えと着替えだ。大抵ご飯を食べながら力んでいるので、保育園に行く前に必ず替えておく必要がある。個人差はあると思うが、暴れるのでそうやすやすとは替えさせてくれない。少しでもおとなしくしてくれるよう、おもちゃで気を引くが気持ちの強さで起き上がってくる。頻繁にまだ拭いていない生のお尻が床についてしまうのが悩みだ。

7:00

朝食の後片付けと洗い物をする。特に子供の食器類は帰宅してすぐ使うものなので、このタイミングで洗っておかないと後が大変になる。

7:20

ここから自分の準備をするわけだが、保育園の都合もあり7:30には出発する必要がある。以前は朝シャワーを浴びるのが日課だったが、もうそんなものは夢のまた夢だ。ささっと着替えて髪を整える程度で準備を終える。トイレに行く暇はなくはないが、しゃがんでいるほどは時間はない。朝にお腹を下すことの多い私にとって、これは地獄だ。

妻はこのあたりで家を出るのでここからは完全に一人での対応となる。

7:35

満身創痍で準備を終えるも、家を出るのは結局7:35になってしまうことが多い。出発が遅れた分は保育園までの移動で取り戻すしかない。息つく暇もないのだ。保育園に着く頃には、満身創痍を通り越して燃えカスになっている。


これが我が家の朝の日常だ。雨が降った日はもっと深刻な事態となることは想像できるだろうか。考えたくもないのでここで詳しく書くことはやめておこうと思う。

次は夜の話に移る。

保育園のお迎えは妻の仕事だ。17時にお迎えにいき、帰宅後子供に夕食を食べさせ、その後遊んでほしいと泣き叫び暴れる子供とケンカしながら自分たちの夕食を用意してくれている。正直、どうやって作っているのか自分には理解できない。密室殺人なみのトリックがないと不可能だと思う。

19:00

帰宅すると、そうやって苦労して作った夕食が待っている。が、その前に手洗いうがいをして着替えを済ます。

19:15

ようやく夕食にありつける。しかし、ここでもゆっくりは食べていられない。ささっと食べないと、次は子供のお風呂が待っているのだ。

なぜそこまで急ぐのかわからない方のために言っておくと、ここでゆっくりしてしまうとどんどん寝る時間が遅くなっていくのだ。次の日も6時起床が約束されているので早く寝ないとやってられない。

19:45

お風呂は前日の夜に洗ってあり、夕食開始とともに湯沸かしボタンを押す運用になっている。そうすることで、食べ終わったら即入浴可能状態となる。

お風呂は入れる係と外でサポートする係に分けられる。それぞれでノウハウがあるため、役割はどちらかに固定されるのが一般的だと思う。私はお風呂に入れる担当だ。二人でこなす分にはそこまで大変ではないが、これを一人でやろうとすると一気に難易度が上がる。一人で子供二人をお風呂に入れているという話を聞くと、神を見たような気持ちになるのは私だけだろうか。

お風呂から上がったら全身にベビーワセリンを塗り、着替えさせ、水分を取らせる。そして必要に応じて爪を切る。この辺りは妻の仕事だ。

20:15

ここから、いろいろと仕上がった子供を預かり、寝かしつけ始める。絵本を読んだりオルゴールを流したりして、なんだかんだで入眠するのは21時近い時間になる。

20:50

ようやく自由時間だ。

とはならない。ここから保育園で出た洗濯物を洗い始める。お風呂のお湯を使うのでどうしてもスタートがこの時間になってしまう。

洗濯機を待つ間、洗い物をする。これをやっておかないと次の日の子供の朝ごはんに支障が出るのだ。洗い物が終わったら、洗濯が終わるまでしばしの休憩が許される。

21:20

休憩もそこそこに、洗濯物をほす。この作業はテレビを見ながらできるので娯楽に近い。洗濯物をほし終わり、前日の乾いた洗濯物をたたんだところで、一日のタスクが完了となる。

21:40

ここからが自由時間だ。ブログを書いたりメールやFacebookメッセージ等に着信している仕事の連絡に返事をするのはこの時間を利用している。結局仕事をしているので自由時間という自覚はほぼない。

23:00

いろいろ片付いたところで就寝となる。

??:??

ここで終わらないのが育児の大変なところで、このあと数時間おきに子供が泣くのでその対応をする。この時間が最も辛い。

こうして何度か起こされつつ、まとまった睡眠時間を取れないまま朝を迎える。この睡眠不足感を想像しながら、本記事のスタートに戻ってみてほしい。ようやく深い眠りについたタイミングで天使が顔の上を歩いてくるのだ。寝起きのつらさがわかっていただけると思う。

ひとこと

育児は大変だと聞いてはいたが、実際自分がやってみると大変どころの話ではない。本当に過酷だ。

そういえば母は毎日日付が変わるまで家事していた。フルタイムで仕事した上で育児と家事をすべて一人でやっていたのだから、そうなるか。実際の苦労を知り、母への感謝と、父の怠惰さに怒りがわいてきた。

なんて書いていたらもうこんな時間か。明日も早いのでもう寝るとしますおやすみなさい。