くりーむわーかー

プログラムとか。作ってて ・試しててハマった事など。誰かのお役に立てば幸いかと。 その他、いろいろエトセトラ。。。

2019年12月

早すぎる最適化 Computer Programming as an Art

クヌースさんのチューリング賞受賞時のスピーチ?

「早すぎる最適化は諸悪の根源」ってクヌースさんの割と有名な格言?みたいなものがある。

原本読むと、別にここだけが大事な話じゃないよね?って思ったのと、

色々思ったので残しておこう。

原本


最適化ってそこそこ広い意味があるように思う。

クラス構造を整理するのも最適化って言ったりするし、、、

クヌースさんが言うところだと、パフォーマンスとかメモリ効率の

チューニング的な意味だと思う。

で、そういう対応するためのコードってかなり複雑な処理になりがち。

「このメソッドの中でインスタンス化してるの無駄が多いかな~」

とか

「ここはこういう使われ方をする”かも”しれないからこうしておこう」

みたいな。

そういうことを初期の段階から考えるべきじゃないよって感じ?

最初から完璧な状態を作ろうとするんじゃなくて、変更とかテストが簡単に出来るように、

今この時点での要件を満たすようにコードを書くようにする みたいな。

必要なコトを必要な時に必要な分だけやる みたいな。

完璧なモノは最初から作れないんだから、変更しやすいように組んだ方が良いよね。

で、「ここは遅くなるかなー」って思ってたところでも、

実際にはたいして全体には影響がほとんどないでしょ。って事がよくある。

そういう推測だけでのあーだこーだには対して意味がないから、

作った後に、解析用のツールとか使ってどこにボトルネックがいるかを

ちゃんと実測してから性能的なチューニングをしようねと。

「推測より実測」が大原則だよねと。

っていう話だと思うのですが、そーはいっても、ある程度の性能的な観点は

最初っからやっといた方がよいと思う。

例えば、ループの中でSQL毎回発行して、画面開くたびに100回クエリ発行してるとか

ほったらかしにしてると、結構よく出てきません?

このくらいの意識は持った方が良いとは思う。

で、早すぎる最適化はそんな感じなんだけど、原本読んでみると、

それよりもぐっと来たのがあったのでそっちを書いておきたい。

『誰もが「最良の」スタイルは存在しないことを覚えておくことが最も重要です。』

つまりは、

『芸術は分かんないけど、これが好きなのはわかる』

って事。

コーディングのスタイルってたまに聞くけど、

良いスタイルとは何だろう、悪いスタイルは何だろう。

他の人が作った作品について厳しく批判するべきじゃないでしょう。

みんな自分の好みがあるからね。

他人の好みを「変革」するために自分の「偏見」を押し付けると、

良かれと思って指摘しててもそれはその人の好みを無意識に否定しちゃう。

それって楽しくないよね。

その人は自分が美しいと感じモノをその人なりに作ってるんですよ。

そう感じたものが他の人からも有用だと思って貰えたならそれは素晴らしい事だよね。

創造性の余地を残したい。

何かこう、色々見てると、あーしなきゃだめ。こう書いたらダメ。

こう書いてるやつは分かってないとか、デザインパターンが~、とかとか。

プログラミングは確かに型にはめるとすごく楽だし、

多分きっと、生産性も品質も良くはなるんでしょう。

でも、つまんなくね?

その型を考えた人は楽しいかもしれないけど、

やらされてる方は、毎回毎回同じ感じのコード書くだけになるので

はっきりいって苦痛。

こういう統制がとれた美しさみたいなものもあるでしょうし、

そこが美しいと感じる人もいるでしょう。

でも、つまんなくね?

って、自分もそういう枠組みを考える側なのですが、

どうにかして、実際に作ってる人、個々人の創造性みたいなものを

出せるような感じにしたいのです。

ただ単に自由にさせるだけじゃもちろんダメなんですけど、、、製品としてはね。

確かに、ある側面では明らかにダメな実装っていうのは一杯あると思いますが、

それを書いた人が、何でそう書いたのか、それが何でいいと思ったのかは

ちゃんと聞いてみないとですよね。。。

まーでも、コピペでしかPG書いたこと無いような人も結構いるし、

創造性を出せるようにーとか自分が鼻息荒くしたところで、って感じもする。

プログラム作る事を楽しんでないとやっぱダメよね。

「we ought to give the programmer-user a chance to direct his creativity into useful channels.」

プログラマーが自分の創造性を有用なチャンネルに向ける機会を作りましょう。

Python Djangoで動的にモデルを作成

Djangoのモデルはすごく便利なのですが、

固定の定義を作らないとダメなので、

何かの設定とかに応じてモデルを作りたい場合はちょっと使えない。

SQLを自前で書いてゴニョゴニョしてもいいんだけど、

ORMは出来ればDjangoの使いたいのでどうにかしたい。

ということで、モデルを動的に作りたくなった場合。

あと、動的に作ったモデルのテーブルも動的に作りたい場合についても。

参考:https://code.djangoproject.com/wiki/DynamicModels

※すごく古いのでちょっと微妙かも。。。

まず、動的にモデル作る本体は下の感じ。

from django.apps import apps
from django.db import models

def create_model_class(cls, name: str, fields=None, app_label: str = "", module: str = "", options=None):
    """指定されたモデルのClassを作成する"""
    class Meta(object):
        pass
    if app_label:
        setattr(Meta, "app_label", app_label)
    if options is not None:
        for key, value in options.items():
            setattr(Meta, key, value)
    attrs = {"__module__": module, "Meta": Meta}
    if fields:
        attrs.update(fields)
    model = type(name, (models.Model,), attrs)
    return model

普通にクラス定義するときにやってる事を、コードで書いてるだけ。肝は↓のとこ。

type(name, (models.Model,), attrs)

models.Modelを継承したクラス定義にする。

で、どう使うかというと↓の感じ。

app_label = "app"
model_name = "DynamicModelName"
ops = {"db_table": "table_name"}
fields = {}
fields["id"] = models.AutoField(primary_key=True, null=False)
fields["test01"] = models.IntegerField(null=False, default=0)
fields["test02"] = models.CharField(null=False, max_length=255, default="")
ret = create_model_class(
    name=model_name,
    app_label=app_label,
    module=".".join([app_label, model_name]),
    options=ops,
    fields=fields
)

obj = ret()
obj.test01 = 123
obj.test02 = "abc"
obj.save()

q = ret.objects.all()
q.values()

ただ、動的に作ったモデルはDBにはまだテーブル作られてないので、

save()とか、objects.all()とかはもちろん動ない。

テーブルをこの作ったモデル定義が作成する場合はschema-editorを使う。

こんな感じ。

from django.db import connections

connection = connections[con_label]
with connection.schema_editor(collect_sql=False, atomic=True) as se:
    se.create_model(model)  # ここでCreate Tableが発行される

SQLだけ拾いたいときは下の感じ

with connection.schema_editor(collect_sql=True, atomic=True) as se:
    se.create_model(model)
    "\n".join(se.collected_sql)

collect_sqlをTrueにすると、操作に対してのSQLがcollected_sqlのlistに

たまってくので、最後に「"\n".join(se.collected_sql)」で全部取るみたいな。

collect_sqlをTrueにした場合は、create_model()とかしても、SQLは発行されない。

あと、作ったモデルは↓の感じで使いまわせる。

from django.apps import apps

django_model_name = model_name.lower()
if django_model_name in apps.all_models[app_label].keys():
    ret = apps.all_models[app_label][django_model_name]

q = ret.objects.all()
q.values()

「apps.all_models」がDjangoがmodel定義を全部キャッシュしてある場所。

削除したい場合はここから消せばいい気がするんだけど、

ちゃんと調べてないので、要調査。今のとこ使い終わった場合は↓の感じで消す。

def unload(cls, app_label, model_name):
    django_model_name = model_name.lower()
    if app_label in apps.all_models:
        mdic = apps.all_models[app_label]
        if django_model_name in mdic.keys():
            mdic.pop(django_model_name)
            apps.clear_cache() # これないとapps.get_models()から消えてない

あと、外部キー制約がどうもうまくいかなかった。

他にも上手くいかないオプションはあると思うのですが、

単純なテーブルのORMやるだけなら十分。。。

と、ここまで書いてて、こんなパッケージがある事に気づいた。

後でコード見ておこ。。。

問合せ