さるへい備忘録

さるへいのやったことを綴っているブログです。基本的にテクノロジーの話題です。

デフォルト引数に関数を使う場合の注意点

Pythonの関数定義に欠かせないデフォルト引数ですが、とある罠にひっかかったので備忘録として残しておきます。

まずは、以下のようなコードを用意します

# coding: utf-8
import datetime
import time


def arg_test(message: str, now=datetime.datetime.now()):
    print(message)
    print(now)


if __name__ == "__main__":
    arg_test('デフォルト引数利用1')
    arg_test('デフォルト引数非利用1', datetime.datetime.now())

    time.sleep(1.5)

    arg_test('デフォルト引数利用2')
    arg_test('デフォルト引数非利用2', datetime.datetime.now())

datetime.datetime.now() は、現在時を取得するコード、 time.sleep(1.5)は1.5秒待機する簡単なコードです。

  1. デフォルト引数を利用、非利用で関数をそれぞれ1回実行。非利用時は、引数で現在時を入力。
  2. 1.5秒待機
  3. 1と同じことをする。

ここまでくればなんとなくわかるでしょう。
このデフォルト引数の関数が評価されるタイミングを僕は見誤っていたという話になります。
ちなみに、実行結果は以下のようになります。(実行時は 2021-12-20 14:38:16.655634

デフォルト引数利用1
2021-12-14 20:38:16.655634
デフォルト引数非利用1
2021-12-14 20:38:16.655696
待機開始
待機終了
デフォルト引数利用2
2021-12-14 20:38:16.655634
デフォルト引数非利用2
2021-12-14 20:38:18.156938

デフォルト引数を利用した方は、スリープしても表示される現在時が一切変わりません。
つまり、デフォルト引数に関数を渡した場合は、普通に引数に関数を渡した場合と関数の評価タイミングが異なるということになります。

Pythonの公式には以下のように記載されています。

docs.python.org

デフォルト引数値は関数定義が実行されるときに左から右へ評価されます。

とあります。つまり、関数を定義する際に引数の関数は実行され、 その時の値がひたすら使い回されるという形になります。
まだ単体のすぐ終わるスクリプトならいいですが、webフレームワークを利用してるときなどは、サーバ開始時の評価結果がひたすら入るので注意したほうがいいですね。

また、インスタンスなどを作成する際も以下のような事故は起こりそうで個人的には怖いなと思いました。

# coding: utf-8
from dataclasses import dataclass

outer_target = 4


@dataclass
class ArgTestClass():
    target_arg: int = 3

    def update_arg(self, num: int):
        self.target_arg = num

    def print_arg(self, target=target_arg):
        print(target)

    def print_outer_target(self, target=outer_target):
        print(target)


if __name__ == "__main__":
    ins: ArgTestClass = ArgTestClass()

    print('更新前')
    ins.print_arg()
    ins.print_outer_target()

    ins.update_arg(5)
    outer_target = 6

    print('更新後')
    ins.print_arg()
    ins.print_outer_target()

実行結果

更新前
3
4
更新後
3
4

こちらも定義後に値を更新してるけど更新されないといった形ですね。インスタンス変数をデフォルト値にいれるのはあんまりないかもしれませんが、外部のスコープの方はなんとなく使ってたらやらかしそうな感じがします。

僕は、デフォルト値にはできるだけ静的な値を使うを心がけて生きていこうと思います。

S3で画像をバックアップしてみようの巻

前回

saruhei1989.hatenablog.com

の記事の最後に、レプリケーションはバックアップじゃないとお話しましたね。
そう、バックアップじゃないのです。でもS3サーバのバックアップほしいときってありますよね?
僕はほしいです。

ということで、なんとかしたかったので色々考えてみました。

最終目的

僕のほしいバックアップの最終形態の要件は、

  • バックアップしたいバケットに入っているファイルと同じものがバックアップ先にもはいっている
  • 間違えて変更、削除しちゃったりしてからしばらくたってもすぐ復旧することができる

になります。後者が現状のレプリケーション機能だと難しいわけですね。非同期プロセスで迅速に同期されてしまうので、うっかりに数時間気が付かなかった場合など大変なことになります。 例えば、一括変更して間違えて多数のファイルを書き換えてしまった場合などですね。

どんなバックアップができればよいか

前述の目的を叶えるには

一定期間頃に目的のファイルを格納した場所をつくる

でだいたい問題ありません。一定期間をだいたい1週間毎にしておけば、1箇所しかバックアップがなくてもだいたい7日間は割と事故前に近い状況に戻せるというわけですね。
お金に余裕があれば1日ごとのバックアップを7日分確保とかすればよいでしょう。

どうするか?

ここで本題です。バックアップの手法っていろいろありますよね。 よく聞くやり方だと

といったやり方がありますね。それぞれの手法は調べてみてください。
そして、今回は増分バックアップを採用しました。理由としては、フルバックアップだと通信費とかがバカみたいに毎回かかる。差分バックアップはバックアップからの復帰機能をつくるのがしんどそう。といった形です。

具体的な方法

最初に今回の手法の全容を箇条書きにしちゃいます。

  1. (初回のみ)初期状態として、バックアップ元をバックアップ先へバックアップとる回数分フルバックアップする。7個バックアップ作りたい場合は7個のバックアップ先をつくって7回フルバックアップするという感じですね。
  2. 設定した期間の増分、変更分を取得してその分だけ変更をバックアップ対象に適用する。

はい。簡単ですね。それぞれどうやったかを説明します。

初期状態として、バックアップ元をバックアップ先へバックアップとる回数分フルバックアップする

これは非常に簡単です。バックアップ対象をそのままコピーなりなんなりすればいいだけです。
だいたいはマネジメントコンソールのディレクトリまとめてコピーでなんとかなりますが、メタデータもコピーしたい場合は AWS CLIのオブジェクトコピー機能でがんばってメタデータまでコピーしましょう。
僕はいらなかったのでマネジメントコンソールで男気コピーです。

設定した期間の増分、変更分を取得してその分だけ変更をバックアップ対象に適用する。

これがやっかいでした。

変更分をどうやって取得するかって結構難しいんですね。 最初はAWS CLIでなんとかなるだろって高をくくっていたのですが。

docs.aws.amazon.com

こんな感じで、ファイル一覧を取得するにも、取得できるファイル数に上限があるんですね。正直数十万とかファイルがあると非現実な方法になってしまいます。

そこでAWSテクニカルサポート先生にお聞きしてみました。さすがテクニカルサポート先生ですね、さすがの答えを教えてくれました。

docs.aws.amazon.com

S3インベントリ機能です。これは単純に指定した期間ごとに対象に格納されているファイルの一覧をCSVで出してくれるって機能です。
つまり、AWS CLIで何回もファイル一覧をページングして頑張って取得する必要がないということです。
これで変更分の取得も解決しました。

最終的にはこんな感じです。

f:id:saruhei1989:20211210161405p:plain
構成図

バックアップ先を複数もっておけば、複数期間のバックアップもできちゃいますね。
これは、結構ニッチな要件かもしれませんが、S3を画像置きにしている際などはなかなかに活躍してくれると思っています。

S3のレプリケーション機能

S3のオブジェクトレプリケーション機能というのがあるので、備忘録がてら解説してみます。

docs.aws.amazon.com

こちらは、簡単にいうと、S3に格納しているオブジェクトを別のバケットなどにレプリケーションしてくれる機能です。
リードレプリカを作成するような感じですね。バックアップとは違うことを注意しましょう。
バージョニングなどのメタデータも完全にレプリケートされるので、そういった要件がある場合は非常に嬉しい機能になります。

レプリケートされないもの

ただ、完全に同じものをレプリケートするかというと以下の部分が違う模様。

  • 削除されたオブジェクト

こちらは、同じく削除されるというわけではなく、削除マーカーがつくだけのようです。
削除リクエストを出せば削除されるという仕様のようですね。

また、DBのレプリケート同様にオブジェクトがレプリケートの対象なので、入れ物の設定はコピーされません。

レプリケートの要件

参考 : オブジェクトのレプリケーション - Amazon Simple Storage Service

必要な項目については以下のように記されています。

オブジェクトのレプリケーションを有効にするには、レプリケーション設定をレプリケート元バケットに追加します。最小設定では、以下を指定する必要があります。

僕がやろうとしたところ、追加でオブジェクトのバージョニングも必要だったので気をつけてください。

また、レプリケート開始時には以下の情報が必要になります。

  • レプリケート元バケット

  • 送信先バケット

  • レプリケートする推定ストレージボリューム (テラバイト単位)

  • レプリケートするストレージオブジェクトの推定数

推定ボリュームやオブジェクトの推定数が結構難しかった記憶があります。
最初から想定しきるのは不可能なので、できるだけで良いようです。

また、既存のバケットレプリケーションする際はサポートに前述の要件を満たした上でレプリケート元バケットなどの前述の4情報を伝えて既存オブジェクトをレプリケートしてもらう必要があります。

まとめ

オブジェクトをレプリケートする という要件だととても良いものですね。なんのデメリットもないので検討してる方は全然採用しても良いかと思います。
ただ、バックアップには向きません。 CloudWatchにログが記載されるようですが、どうやら細かいオブジェクトの情報までは記載されません。(参考 : https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/replication-metrics.html

オブジェクトの細かい情報まで追っていけばバージョンの情報や削除マーカーがあるのでできなくもないですが、ちょっと非現実的ですよね。間違えて書き換えてしまったとかその辺りはカバーするのはとても難しいです。
僕はそもそもバックアップしたいなぁとおもってレプリケーション機能を調べていたので、次回は ぼくのかんがえたS3ばっくあっぷ計画 となります。

フロントエンドのリクエストに渡す値のテストについて

今回は短い備忘録です。

普段フロントエンドでAPI通信する際は、皆さんmockを使っていると思います。
よく見るのが、完全にmockにして、ハードコードで値を返してしまうといったテストです。
Pythonなどのバックエンドなら、渡す値も割とコードで明確に示せるので良いのですが、フロントエンドの開発だとGUIコンポーネントの値がガンガン変わっていったりするので果たして正しい値が渡されているのか・・・?といった不安に苛まれたことは一度や二度ではありません。

そこで僕がやってる方法をここに記録として残してみます。

以下はTypeScriptっぽい擬似コードになります。

// asset用の変数を用意
let varForAssert;
// 通信モジュールをmock
const mock = jest.spyOn(通信モジュール, '通信メソッド').mockImplementation((methodName: string, formData: { [p: string]: any }, とか、その後の処理を受け取ったりする) => {
    if(methodName == 'method_A'){
        varForAssert = formData;
    }
});
// 通信を呼び出すような処理を行う
~~ なんかいい感じの処理  ~~
// assert
expect(mock).toBeCalledTimes(呼ばれた回数);
expect(varForAssert).toStrictEqual(期待するデータ)

基本的には

  1. assert用の変数を用意
  2. 通信処理をmockする。mock内ではどの通信かを特定、僕は渡されたデータをassert用変数に格納の2種類は必須処理としている。
  3. 通信処理を行うなにかの処理を実行
  4. assertする

の流れになってます。 mockの中でassertしてもいいかもしれませんね。それは好きにやると良いと思います。

これはリクエストに渡された値のテストだけなので、この処理をいろんなテストに挟み込んで対応しているといった形になります。

SSGやSSRと多言語の組み合わせについて ~ PythonでSSG,SSR①

皆さんSSG、SSRしてますか?
CSRで描画しているサイトなど、描画スピード対策やSEO対策で苦慮した方は大勢いらっしゃると思います。
そういった場合、SSG(Static Site Generator)やSSR(Server Side Rendering) を適用したサイトを考えることになるというのは多いかなと感じます。
ところが、SSGやSSRはnext.jsなどのフレームワークを用いた場合のみ適用できている例しかほぼでてきません。
バックエンドでPythonを扱っている私のような人間からすると、使いたくても使えないみたいな状況が結構でてくるわけですね。

ということで、バックエンドはPython、クライアントサイドはreactでなんとかできないか?
というのを、連載形式でなんとか頑張ってみます。

まずは、CSRSSRとSSGを簡易的に解説します。

CSR(Client Side Rendering)

こちらはそのまま、クライアントサイドで情報を受け取って描画するという話です。
乱暴に表現すると、真っ白なサイトの上でreactなどを走らせて情報をAPIで取得してパズルのようにオブジェクトを配置していくといった手法ですね。
CSRを適用する部分は基本はレンダリング前は無の状態です。

SSR(Server Side Rendering)

こちらは、CSRするにしても情報が多い場合は最初真っ白な画面がでてきてUX的に問題なのではないかな?からでてきた手法です。 最初の描画を真っ白ではなく、サーバ側でHTMLを生成して描画する方法ですね。
その後は、CSRと同じ方法で描画が遷移していきます。
要するに、最初は何かしらが描画されている手法です。

SSG(Static Site Generator)

急になんか名詞になりましたね。こちらは、SSRにしても毎回データベースアクセスをするのは無駄なページがあるのでは?みたいな話から発生した手法のようです。
単純に、事前にHTMLファイルのような静的ファイルを定期的に作成しておいて、ユーザからは静的ファイルを見るだけでOKにするといった手法です。
情報の更新が頻繁ではないページに対して適用すると効果的な方法ですね。

つまりどういうこと?

最近流行りのreactやVueなどのフレームワークは基本的にはCSRフレームワークです。
VirtualDOMを用いた描画更新といった非常に優れた状態管理を実現していますが、考えずに使うと初回描画時に弊害が起こる可能性がそれなりにあるということですね。
初回描画時の状態というのはUXやSEOでとても重要な要素です。GoogleBotは比較的最新のブラウザの描画と同条件とGoogleは話していますが、インデックス時に果たしてどこまで描画されてるかは名言されていません。
じゃあSSRやSSGを使いたいなって思いますよね。

。。。でもこれは冒頭に記載したとおりnext.jsなどの選ばれしフレームワークでしかサクッと実現できる話ではないというのが僕の知ってる現状です。

そこをなんとかPython使いの僕でもなんとかしたいねというのがこの連載のモチベです。

今回はここまで、次の回は具体的な計画をしてみます。

headタグとheaderタグ

みなさん head タグ使ってますか。 僕はめっちゃ使ってます。

最近 header タグというものの存在を知って色々混乱したので備忘録的にに残したいと思います。

この記事を読まなくても、 head タグと header タグという全く別物があることは頭の中に残しておいてください。

head タグとは?

参考:

developer.mozilla.org

developer.mozilla.org

上記から、 head タグとは、人に読ませる目的ではない title タグやOGPタグといったmetaデータを格納する箇所であるとわかります。
記載場所は html タグ配下になっています。

ここで厄介なのが、

要素内で最初に存在するものが要素である場合、開始タグを省略可能。
要素に続く最初のものが空白文字やコメントでない場合、終了タグが省略可能。

といった仕様。
つまり、うっかり許可されていないタグなどを記述してしまった場合、強制的にタグが閉じられて headタグ内に記載すべき要素が headタグの外に漏れ出てしまいます。
許可されているのは以下のタグになります。

<title>
<base>
<link>
<style>
<meta>
<script>
<noscript>
<template>

最近は head タグ内に記載することを指定されているタグも、うっかり body タグに記載してもちゃんと動く事が多いです。しかし、それは結局想定されていない利用方法になってしまうので、何が起きても文句は言えません。
うっかりwebページ全体に悪影響がでてしまう事故が起きてしまう可能性があるということです。
webメディアを運用する人はこちらの仕様はしっかりと頭に入れておいたほうが良いでしょう。

header タグとは?

参考 :

developer.mozilla.org

head タグと名前がよく似たこちら、 必須なタグではなく、 例えば、 body タグの内部に設置しても大丈夫なタグです。
基本的に内部に header タグや footer タグを除いたすべてのタグが記載可能であり、勝手に閉じることはありません。
<div class="header"></div> みたいに記載してたやつの代わりに使えそうなタグですね。

これだけだったら超名前似てるけど使い方全く違うややこしいタグがあるで済みます。
まぁそれでも検索でうっかり head タグと header タグの調査結果を取り違えるなどはありそうなので、本当にこの名付けは個人的には勘弁してほしいものです。

さて、ここで前述の参考(https://developer.mozilla.org/ja/docs/Web/HTML/Element/header)から、衝撃的な文言を見つけました。

要素は HTML5 まで仕様書には現れていませんでしたが、実は HTML の最初期に存在していました。 the very first website に見られるように、元は 要素として使用されていました。ある時点で、別な名前を使用することが決定されました。これによって
が自由になり、のちに別な役割を担うことができるようになりました。

つまり、元は head 要素として使われてたってことですね。 昔のHTMLはつまりいつの間にか事故の起こるHTMLという話になっていたようです。
これはちょっと個人的にはひどいなぁとは思います。なんで昔別の用途で使われてたものをこんな再利用の仕方をしたのか。。。

今の時代、うっかり取り違えても表示やパースで大事故がおこるというのはそうそうなさそうですが、HTMLの仕様をすべて追えてる人ではないと確実にひっかかりそうな話です。

最後に

もちろん、ちゃんとHTMLの仕様を追っていた人には関係のない話です。
不勉強なやつらめ、で済む話ですが、個人的にはもう少し別の名前をつけてほしかったなぁと思う今日このごろ

GoogleChromeのUser-Agent凍結ではなくUser-Agent Reduction

Google ChromeによるUser-Agent凍結という話を覚えてらっしゃる方はいらっしゃると思います。

実は、いまではUser-Agent凍結ではなくUser-Agent Reduction。つまりもとの凍結とは違い、削減として計画されています。

参考 : www.chromium.org

自分の備忘録もあわせて今後どう計画されているかを解説していきます。
なお、執筆当時の私の解釈なので本当にあってるかは元の記事などを読んでみてください。私は英語が得意なわけではないので解釈が正しいとは限りません。

そもそもなんでUser-Agentを凍結するという話がでた?

以下の記事が参考になると思います。

groups.google.com

こちらの記事から、主にプライバシーの問題であると読み解けます。
現在のUser-Agentは、フィンガープリンティングという広告などで利用されるいくつかの情報を組み合わせて個人を特定する手法に利用されているといいます。そういったことを問題視しているのでしょう。
昨今のCookieに対する制限もそうですが、個人を特定する要素をできるだけ減らす方向にGoogleが舵を切っている印象です。

じゃあ、User-Agentを削除すればいいじゃないか?と考える方もいらっしゃると思います。
最初はその予定だったとは思いますが、他のブラウザとの兼ね合いもあったのだろうなぁと僕は想像しています。
User-Agentを表示のための判断基準につかっているサイトがChromeだけで動かないんだったらだいたいの人はおそらく別のブラウザを使うでしょうし、いきなり削除というのはやはり急すぎたのだろうなぁと思います。

GoogleChromeのUser-Agent削減計画

GoogleChromeでは、User-Agent削減において、以下のような計画を立てています。
参考 : https://www.chromium.org/updates/ua-reduction

削減の準備

  • フェーズ1 : Chrome92の開発ツールで各種User-Agentを取得する機能に対して警告をする。
  • フェーズ2 : Chrome95からChrome100まで、少なくとも6ヶ月のオリジントライアル(実際にブラウザを使ったトライアル)を実施する。執筆当時はここ。

削減のロールアウト

  • フェーズ3 : Chrome100 : 逆オリジントライアル。つまりオリジントライアルの内容を徐々に反映していく判断をします。少なくとも6ヶ月の期間をかけます。
  • フェーズ4 ~ フェーズ6 : 後述します。ここで実際にUser-Agentの削減がロールアウトされます。
  • フェーズ7 Chrome113 : 完全終了です。

上記の通り、長めに準備期間をとり、その後実際に削減するといった方法をとっていきます。
実際にいまオリジントライアルに入っているのですが、割とそのことを知っている方は少ないように見受けられます。
このままではドタバタが発生しそうな気も。。。

現在のUser-Agent

では、ここで現在のUser-Agentにはどのような要素が入っているのが確認してみましょう。
参考 : https://www.chromium.org/updates/ua-reduction

デスクトップ

Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.1234.56 Safari/537.3

モバイル

Mozilla/5.0 (Linux; Android 9; SM-A205U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.1234.56 Mobile Safari/537.36

色分けしたそれぞれの情報は以下の情報を含んでいます。

  • 青 : 製品名
  • 緑 : OS情報
  • 赤 : レンダリングエンジンなどの情報
  • 黄色 : ブラウザの製品名など

どうやら青はもうちゃんと運用されている文字列ではないようで、魔法の言葉のようにとりあえずつけるものといった運用になっている模様です。
Googleがプライバシーの問題と言っているのは、ここの各情報が細かすぎるといったもののようです。OSのマイナーバージョンなんてUser-Agentとして知らせる必要はあるのか?みたいな話ですね。

どのように削減されるのか

さて、本題です。
前述のフェーズ4 ~ 7でどのように削減されるかを示します。
この削減の内容は、Googleがどこまで細かいと個人を特定し得ると考えてるのかを知ることのできる珍しい機会とも考えられます。
参考 : https://www.chromium.org/updates/ua-reduction

フェーズ4

Chrome101での変更とされています。

前述の黄色部分。つまりブラウザ情報が

Chrome/93.0.1234.56 Mobile Safari/537.36

から

Chrome/93.0.0.0 Mobile Safari/537.36

となります。デスクトップもモバイルも共通ですね。つまり、マイナーバージョンが消えます。

フェーズ5

Chrome107での変更とされています。

前述の緑部分、つまりOS情報がかわります。前述の例だとデスクトップになります。

(Windows NT 6.3; Win64; x64)

から

(Windows NT 10.0; Win64; x64)

となります。いままでOS + cpu情報になっていたものが、統一プラットフォームの情報に置き換わります。
例えば、Macだとすでに統一プラットフォームの情報らしく、詳しく知りたい方は元記事を読むとわかりそうです。

フェーズ6

Chrome110での変更とされています。ここでは、AndroidのOS情報がバージョンに関わらず固定となります。

(Linux; Android 9; SM-A205U)

から

(Linux; Android 10; K)

となります。どんなAndroidの情報もこれに固定化されます。

終結

デスクトップ

Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.0.0 Safari/537.36

モバイル

Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.0.0 Mobile Safari/537.36

最後に

あれ?思ったより影響が少ないぞ?と思った方もいらっしゃると思います。
実際フィンガープリンティングなどを用いて個人を特定するなどない限り今回の影響はかなり限定的ですね。
モバイルかデスクトップかの判断にUser-Agentを用いてる程度なら影響は軽微ですむのではないでしょうか。

とはいえ、他のブラウザなども足並みを揃えてきた結果削減ということもおおいに考えられるので、webエンジニアの皆様は他のブラウザの状況も含めてUser-Agentの情報は追っていったほうが吉でしょう。