私が歌川です

@utgwkk が書いている

退勤して、ラーメンを食べてベッドに倒れ込んだら猛烈な眠気が襲ってきた。朝の5時ぐらいに目覚めてインターネットをしていたのと、仕事の疲れがあったのとが重なったのだろう。


冷感シートが届いたので試してみたところ、たしかにちょっと冷える気がする。が、汗はしっかりかくのでもうちょっと調整が必要そう。

テストケースと野生の勘

コードを書いていたりコードレビューで実装を眺めたりしていて、こういうケースでもうまく動いてほしいけど落ちそう、と思ってテストケースを足すと実際にテストが落ちる、ということがしばしばある。はたから見ると野生の勘とか嗅覚みたいな感じになりそうだけど、コツはあるはず。

条件分岐や追加された実装からアタリをつけるのは1つのコツだと思う。実装が増えるということは、考慮しないといけない場合が増えるということである。追加された実装を起点に、考慮漏れがないかを探ってみる。エッジケースに見えるかもしれないけどありうるユースケースを考えて、それを実現するテストケースを足して、テストを走らせてみる。落ちればアタリだし、落ちなくても保険としてテストケースを足しておくのがよいと思う。競技プログラミングをやっている人にとっては、撃墜ケースみたいな形で言語化されていそう。

アプリケーションの核となる機能や、壊れると体験を著しく損うので壊れてほしくないようなパーツには、相応のテストがあるとよさそう。なんでもアサーションするべきというよりは、最も保証したい性質を確かめていくというイメージでやっている。確かめていたつもりだけど実は不十分だった、ということにあとから気づくこともあるけど、気づけるようになるのが尊いと思う。


以前も野生の勘について書いていた。野生の勘が言語化・体系化されてパターンになっていくのだろう、と考えている。

blog.utgw.net

緊急事態宣言

まずはこちらをご覧ください。

この写真を再現したくなり、緊急事態宣言ジェネレーターを作る運びとなりました。

「緊急事態宣言」の文字が大きすぎて折り返されると、無料ジャバのダウンロード感が出てきてしまうので、できるだけ折り返されないように注意しました。vwやvhを駆使しています。スマホで見てもひどいことにはならないと思うけど、縦画面で見てもわけがわからないと思います。

ほんとうはスクランブル交差点の写真を使いたかったけど、権利的にクリーンにしたいので、去年撮影した大阪の写真をデフォルトで表示するようにしています。

好きな写真で緊急事態を宣言したくなった際にご利用ください。glitchで作って、remixボタンもあるので誰でも自由に改造できます。もっとかっこいい緊急事態宣言にしてください。

2021/7/8 10:15 追記

suzusime-log.hatenablog.jp

もっとかっこいい緊急事態宣言が作られました。CSSの filter でSVGフィルタが使えることを (そもそもSVGフィルタ自体も) 知らなかったです。使いこなせるとどう考えても便利ですね。

2017/7/8 11:02 追記

blog.sushi.money

現代はCSSでできることが増えていて便利な時代になりましたね。CSSアニメーションだけでなめらかにアニメーションさせやすい属性とそうでない属性がありそう。

あ、これは切ったわ、と思ったときにはすでに切り込みが入っていた。絆創膏を貼ったところを夕方になって剥がしたらシワシワになっていた。


雨が続いていてなかなか洗濯できずにいる。バスタオルが尽きたのでちょっとずつ洗濯機を稼動させて干してみているけど、今日はとくにひどい雨だったのでぜんぜん乾いてなかった。乾燥機を導入しないといけないのであろう。


気がついたら手にラメ (マニキュア?) を塗られていて、そのまま何週間か過ごしたけどまだちょっと残っている。ほんとうは除光液かなにかを使うのがよいのだろうけど、まあもういいか。

環境変数から設定値を読み取るPythonのメタクラス

アプリケーションの設定を環境変数経由で渡すし、個別の設定にはデフォルト値がある、というシチュエーションを考える。

素朴には os.environ.get("FOO", "default_value") みたいなのを書けば設定値を取得できる。が、これにはいくつか問題がある。

まず、あまりに素朴に実装すると os.environ.get("FOO", "default_value") がコピペされまくることになる。これでは環境変数の名前やデフォルト値を修正したくなったときに確認すべき箇所が多くなる。

設定値を書く用のファイルを用意して foo = os.environ.get("FOO", "default_value") みたいに書きまくると多少はましになる。が、まだ環境変数名とPythonでの変数名を重複して書いているのでもうちょっと短く書きたい。また、たとえばテストなどで環境変数の値を書き換えるときも、importの順序次第でうまく書き換えられないことがある。

環境変数から値を読んで返す関数を定義すれば値を書き換えられるようになるけど、設定値を読み取るためのコードが長くなってしまう。

ということで、表題のソリューションを用意した。まずはこちらをご覧ください*1

import os
import re
from typing import List

from app.util import bool_from_env_var

VARIABLE_PATTERN = re.compile(r"[a-z][0-9a-z_]*")


def _create_property(env_var_name, default_value, var_type="str"):
    def getter(self):
        if var_type == "bool":
            # 環境変数の値 (文字列) をいい感じにboolに変換する
            return bool_from_env_var(os.environ.get(env_var_name, ""))
        elif var_type == "int":
            return int(os.environ.get(env_var_name, default_value))
        else:
            return os.environ.get(env_var_name, default_value)

    def setter(self, new_value):
        if isinstance(new_value, bool):
            os.environ[env_var_name] = "1" if new_value else None
        else:
            os.environ[env_var_name] = str(new_value)

    return property(getter, setter)


class ConfigFromEnvironmentVariableMeta(type):
    def __new__(cls, classname, bases, dic):
        variables: List[str] = [k for k in dic if VARIABLE_PATTERN.match(k)]
        new_dic = {k: v for k, v in dic.items() if k.startswith("__")}

        for variable in variables:
            default_value = dic[variable]
            if callable(default_value):
                new_dic[variable] = default_value
                continue

            env_var_name = variable.upper()
            var_type = type(default_value).__name__

            new_dic[variable] = _create_property(env_var_name, default_value, var_type)

        return type.__new__(cls, classname, bases, new_dic)


class ConfigFromEnvironmentVariable(metaclass=ConfigFromEnvironmentVariableMeta):
    pass

ConfigFromEnvironmentVariable クラスを継承した設定値用のクラスを作って、以下のように使うことができる。直接 metaclass を指定してもよいけど、メタクラスよりは継承のほうが馴染みのある概念だと思うのでラップした。

import os


class AppConfig(ConfigFromEnvironmentVariable):
    # 環境変数が設定されていない場合のデフォルト値を指定できる
    base_url = "http://localhost:3000/"
    debug = False


app_config = AppConfig()

app_config.base_url # => "http://localhost:3000/"

# 値をセットすると設定値も書き換えられる (裏では環境変数を書き換えている)
app_config.base_url = "http://localhost:3000/test/"
app_config.base_url # => "http://localhost:3000/test/"

# 型をよしなに見て空気を読んでくれる
app_config.debug # => False

app_config.debug = True
app_config.debug # => True

原理

__new____init__ の前に呼び出されるクラスメソッドで、いろいろ引数を受け取るけど注意すべきは第3引数の dic である。

dic にクラス変数とその値が入っているので、デフォルト値と型を読み取ったあとpropertyに置き換えている。クラス変数以外も入っているので正規表現マッチでフィルタリングしている。ただしメソッドや __ で始まるフィールドは保持している。

property クラスでアクセサを定義している。デコレータとして使うことが多いかもしれないけどこういう使い方もできる。

原理を完全に把握するにはCPythonのコード*2に立ち入るしかなさそうだった。公式のドキュメントを読んでみたけど __new__ メソッドの引数についての言及を見つけられなかった。

参考

以下の記事を見つけて大いに参考にさせてもらった。紹介されているのはPython 2のコードだけど、ちょっと雰囲気を揃えたらPython 3でも動く。

www.yunabe.jp

2022/4/24 追記: 同様のライブラリを探した

myenvというライブラリがまさに同じようなことを実現できる作りになっていそう。実装もだいたい似たようなことをやっているようだった。