くりーむわーかー

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

Python matplotlibのインストールでグダる

Pythonでグラフ表示したくなったので、matplotlibをインストールしようとしたら

かなりグダったので残しておく。

matplotlibの公式

OSはCentOS7

pip install matplotlib

↑で基本は良いようですが、これだけだとCentOSでは動かない。

GUI系の処理をするのにtcl/tkを使ってるらしく、

それのPython用モジュールが標準だとPython3側にはないっぽ。

なのでPython3用のやつをインストールする。

sudo yum install tcl -y
sudo yum install tcl-devel -y
sudo yum install tk -y
sudo yum install tk-devel -y
sudo yum install tkinter -y
sudo yum install python-matplotlib-tk -y

なんだけど、これでもtkinterが入らない。

探してみると

> yum search tkinter

python3-tkinter.i686 : A GUI toolkit for Python
python3-tkinter.x86_64 : A GUI toolkit for Python
python34-tkinter.x86_64 : A GUI toolkit for Python 3
python35u-tkinter.x86_64 : A GUI toolkit for Python 3
python36-tkinter.x86_64 : A GUI toolkit for Python
python36u-tkinter.x86_64 : A GUI toolkit for Python

なので、

sudo yum install python36u-tkinter -y

でやってみると、下の感じでモジュールが競合してんぞって怒られる。

・・・
file /usr/lib64/python3.6/xml/sax/__pycache__/xmlreader.cpython-36.opt-2.pyc from install of python36-libs-3.6.6-5.el7.x86_64 conflicts with file from package python36u-libs-3.6.7-1.ius.centos7.x86_64
・・・

で、これを解消するには一回Python3をアンインストールする必要があるっぽい。

コワ。

なので

sudo yum remove python36u

sudo yum install python36u
sudo yum install python36u-devel

↑でアンインスト+インストしなおし。

そしたら、↓でOK。

sudo yum install python36u-tkinter -y

長かった。

あと、途中でAggはもう使えないよってエラーが出るので、

matplotlib.use('TkAgg')

を入れてみたり、↓の設定ファイルを書き換えたりする必要がある

vi ~/pyenv/lib/python3.6/site-packages/matplotlib/mpl-data/matplotlibrc

## 'module://my_backend'.
backend      : Tkagg

って事をみたのですが、インストールが上手くいった後だと、

特にやらなくてもちゃんと動いた。なんでしょね。

早すぎる最適化 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やるだけなら十分。。。

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

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

Swaggerのアレコレ

Swaggerを使ってAPIのドキュメントを作る話。

公式

Swagger-EditorとかUIとか使えばとりあえずすぐ使える。

公式のツール群は素晴らしいと思うのですが、

ある程度規模が大きくなると、ちょっと無理。

何でかって言うと、ソースが1ファイルだから。

大きくなると行数とか普通に万超えるので。

メンテが無理。

あと、Swagger-UIって1ページに全部ずらっと並べるので

数が多くなってくるとますます無理。

なので、ソースのyamlファイルを分割するのと、

静的なHTMLファイルを作るコンバータみたいなのが欲しい。

と思ってたらやっぱりありますね。

色々あるのですが、公式がまとめてる一覧はこちら

で、最終的に下記の構成にした

Yaml⇒HTMLのコンバータはどれもYamlファイル自体は1ファイルにまとまってないとダメなので、

分割したYamlを一つのファイルにまとめるツールが必要。

で、それをやるのに「multi-file-swagger」と「swagger-merger」があるのですが(他にもあると思うけど)、今回は「swagger-merger」を利用。npmのサイト的にこっちの方がちゃんとドキュメント書いてそうだったので。そういうの大事。

npm install --save-dev swagger-merger

# 実行はこんな感じ
swagger-merger -i ./src/index.yaml -o swagger-tmp.yaml

YAMLが出来たらRedocでHTMLにする。

npm install --save-dev redoc-cli

# 実行はこんな感じ
redoc-cli bundle swagger-tmp.yaml

で、これだと修正しながらブラウザで確認できないので、

なんちゃってホットリロードが出来るようにする。

# 必要なモジュール
npm install --save-dev npm-run-all
npm install --save-dev watch
npm install --save-dev light-server

# ソースディレクトリの更新を監視して、更新されたらmakeを流す
watch \"npm run make\" ./src

# HTMLを監視して更新されたらホットリロードする
light-server -s ./dist -p 4000 -w \"./dist/*.html # # reload\"

もろもろやったサンプルはここにあげた。

ファイルの分割の書き方なんかも↑のリポジトリのsrc内参照。

あと、Redocも素晴らしいのですが、若干ん?って思うところもある。

左メニューの各APIへのリンクがsummryになってるのですが、

ここってURLというかYAMLのタグにならないもんだろうか。。。

他のHTML作るツールも軒並みsummary使ってるので、こっちが主流なのかしら?

CentOS8をVirtualBoxでインスト、ついでにMariaDBとRedis

CentOS8が出てたので、VirtualBoxでとりあえずインスト。

centos8のisoをDL

現時点で「CentOS-8-x86_64-1905-dvd1.iso」

VirtualBoxで適当に作成。

新規作成で枠を作ってから、設定でストレージ⇒コントローラ:IDE⇒CDマーク押してDLしたISOを選択してから起動。

インストールの流れはCentOS7と変わらずかな?

ソフトの選択でサーバGUI選ぶのかワークステーション選ぶのか迷うくらい?

そーいえば、.netCoreの開発みたいなアドオンが出てた。あとで試してみよ。

そしたら色々更新。

dnf -y update
dnf -y install epel-release
dnf -y groupinstall "Development Tools"
dnf -y install dkms bzip2 gcc make kernel-devel kernel-headers

CentOS8からyum ⇒ dnfに変わったらしい。

「yum install」でも動くんだけど、dnfへのリンクになってるだけなんだってさ。

で、GuestAddtionのインストールをするのですが、

前はデバイス⇒GustAddtion選んでたと思うのですが、やってみるとエラー。

何でかなと思ったらどうも既にCDが入ってる事になってるらしい。

なので、アクティビティ⇒ファイルでVBox_Guest~~みたいなのがあるので選択してソフトウェアの実行すればOK。

割とハマらずにウィンドのリサイズとかクリップボードの共有とかできるようになってる。

よかたよかた。

ほいで、CentOS8はPython3がデフォになってるそうで。

でもコマンドが「python3」みたいなので、うーん。

いつも通り仮想環境作るか。。。

そしたら色々触ってみましょかね。。。

ひとまず、MariaDBとRedisをインストする。

MariaDB

MariaDBなんだけど普通にやるとまだCentOS8に対応してないよーって出る。

なので↓。

# dnf localinstallで必要なrpmをもってくる
curl -O https://downloads.mariadb.com/MariaDB/mariadb-10.4.8/yum/centos/mariadb-10.4.8-rhel-8-x86_64-rpms.tar

# 解凍
tar xvf mariadb-10.4.8-rhel-8-x86_64-rpms.tar

# 解凍先に移動
cd mariadb-10.4.8-rhel-8-x86_64-rpms

# インストール
sudo dnf localinstall -y galera-4-26.4.2-1.rhel8.0.el8.x86_64.rpm MariaDB-client-10.4.8-1.el8.x86_64.rpm MariaDB-common-10.4.8-1.el8.x86_64.rpm MariaDB-server-10.4.8-1.el8.x86_64.rpm MariaDB-shared-10.4.8-1.el8.x86_64.rpm

# サービスの有効化と起動
sudo systemctl enable mariadb
sudo systemctl start mariadb

# rootのパスワードとかの設定(最初にパスワード聞かれるけど、空のままエンター)
sudo mysql_secure_installation

#いちを再起動
sudo systemctl restart mariadb

MariaDBは10.4.8じゃないとCentOS8で動かせない様子。

新しい分には別に良いか。。。

Redis

Redisは何か楽になった。

Redis5.0っぽい(2019/11/2)

# インストはこれだけでイケた
sudo dnf install -y redis

# サービスの有効化と起動
sudo systemctl enable redis
sudo systemctl start redis

# 動作確認
redis-cli

# ↑のクライアント上で↓打って PONGって出てくればOK
ping

# いったん抜ける
exit

# ちょろっと設定
sudo vi /etc/redis.conf 

bind 127.0.0.1
↓
bind 0.0.0.0

requirepass  <パスワードを入れる>

# 再起動
sudo systemctl restart redis

# もう一回クライアント
redis-cli

# pingでこんどは「(error) NOAUTH Authentication required.」って出ればOK
ping

# そしたらログイン
auth <上で指定したpassword>

# もっかいpingで、PONGって出ればOK
ping

両方ともインストールはなんか楽になりましたね。。。

C# HttpClientでApacheにPOST送ったら502

すごいはまった。

こんな感じで、JSONデータをPOSTするリクエスト。

string url = @"http://localhost/****";
using (var client = new HttpClient())
{
    HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, url);
    request.Content = new StringContent(@"{***}", Encoding.UTF8, "application/json");
    var response = await client.SendAsync(request);
    var restext = await response.Content.ReadAsStringAsync();
    Console.WriteLine(restext);
}

そしたら、結果が↓になる。サーバはApache。

502 Proxy Error

リバースプロキシでバックエンドに飛ばしてるんですが、

Apacheのログをdebugレベルにしてログを見ると、

# access.log

"POST /hogeurl HTTP/1.1" 502 341 "- - - - - 202 Keep-Alive"

# error.log

[proxy_http:error] [pid 14412:tid 1408] (20014)Internal error (specific information not available): [client ::1:50107] AH01102: error reading status line from remote server 127.0.0.1:3002
[proxy_http:debug] [pid 14412:tid 1408] mod_proxy_http.c(1311): [client ::1:50107] AH01105: NOT Closing connection to client although reading from backend server 127.0.0.1:3002 failed.
[proxy:error] [pid 14412:tid 1408] [client ::1:50107] AH00898: Error reading from remote server returned by /hogeurl

みたいな感じ。何もわからん。

アプリ側のログには何も書かれてないのでそもそも、アプリまで到達してないっぽい。

普通のGetはちゃんと行くし、PostManで同じPOST投げてもちゃんと上手くいく。

という事は、きっと変なヘッダがついてるんだろうと。

で、C#って発行するリクエストの最終的なHeaderってどうやって見ればいいんですかね?

Apache側でログの指定ってヘッダのキー指定で一つ一つ見るしかなさそうなので、

変なヘッダ入ってないか確認するのが出来ないのですが・・・。

良いやり方ないもんか。。。

まーいいや。結論から言うと、C#のHTTPClientで発行するPOSTにはデフォで↓がつくっぽい。

Expect: 100-Continue

拡張ヘッダという事みたいですが、これが悪さしてるっぽい。というかApacheと相性悪い?

これを送らないようにするために、↓にする。

string url = @"http://localhost/****";
using (var client = new HttpClient())
{
    ServicePointManager.Expect100Continue = false;//これ!!
    HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, url);
    request.Content = new StringContent(@"{***}", Encoding.UTF8, "application/json");
    var response = await client.SendAsync(request);
    var restext = await response.Content.ReadAsStringAsync();
    Console.WriteLine(restext);
}

もーね・・・。って感じ。

以下、上に行きつくまでに試した事。

まず、プロキシがおかしいのかとApacheでプロキシまわりのエラーが起きたときに

とりあえずやってみる設定は↓。

# httpd.conf

SetEnv force-proxy-request-1.0 1
SetEnv proxy-nokeepalive 1

そしたら、↓を返すようになった。

417 Expectation Failed

そのほか、POSTManで指定してるヘッダ入れてみたり色々してみて、

この417から「Expect: 100-Continue」に行き着いた感じ。

何かこー、アレな感じですね。。。

バースデーパラドックス

バースデーパラドックス(birthday paradox)っていうのがある。

人が集まった時に誕生日(月日の部分)が同じ人がいる確率はどんなもんかってやつ。

20人くらい集まると意外にも「40%」くらい。

23人になると、「50%」くらいになる。

結構確率高くない?

直観的な感覚と反するのでバースデーパラドックスっていうみたい。

母数がnの場合に、1.18×√nになると50%超えるらしい。

誕生日だと1年365日として1.18×√365(19.1)≒22.53なので23人くらいで

同じ誕生日の人が1組はいるって確率は50%って感じ。

※「自分と」同じ誕生日の人がいる確率じゃなくて集団の中で同じ誕生日の組が存在する確率。

で、実際にやってみた。以下Pythonで書いたコード。

365日で決まった試行回数ランダムに数値を引いて、かぶったのがあったかどうかを

10000回やってみて、どのくらいの割合かを見た感じ。

それを試行回数19、22、23回でそれぞれ5回ずつ。

import random

def challenge(limit):
    tmp = []
    for i in range(limit):
        x = random.randint(1, 365)
        if(x in tmp):
            return True
        tmp.append(x)
    return False

def birthday(n):
    ret = []
    for i in range(10000):
        if(challenge(n)):
            ret.append(1)
        else:
            ret.append(0)
    return ret

if __name__ == "__main__":
    print("n=19")
    for i in range(5):
        ret = birthday(19)
        print(f"{ret.count(1)} / {len(ret)} = {ret.count(1) / len(ret)}")

    print("n=22")
    for i in range(5):
        ret = birthday(22)
        print(f"{ret.count(1)} / {len(ret)} = {ret.count(1) / len(ret)}")
    
    print("n=23")
    for i in range(5):
        ret = birthday(23)
        print(f"{ret.count(1)} / {len(ret)} = {ret.count(1) / len(ret)}")

結果

n=19
3814 / 10000 = 0.3814
3693 / 10000 = 0.3693
3807 / 10000 = 0.3807
3785 / 10000 = 0.3785
3724 / 10000 = 0.3724

n=22
4846 / 10000 = 0.4846
4734 / 10000 = 0.4734
4752 / 10000 = 0.4752
4708 / 10000 = 0.4708
4759 / 10000 = 0.4759

n=23
5047 / 10000 = 0.5047
5058 / 10000 = 0.5058
5092 / 10000 = 0.5092
5007 / 10000 = 0.5007
5079 / 10000 = 0.5079

おお、確かにそんな感じになってる。。。

365程度だと、23回試行すると一回くらいは重複する可能性が50%あると。

100000くらいでも、373回試行で、1回くらい重複する可能性が50%。

割とかぶる可能性高いよね。

そう考えると、小学校のクラスって自分の時は30~40人くらいだったんですが、

割と同じ誕生日の人がいてもよさそうな感じがする。

1学年6クラスくらいあったとしたら6年生までで36組。

同じ誕生日の子がクラスにいるクラスが18クラスくらいあってもおかしくないわけで。

そういえば、高校の時、自分と1日違いの子が2人いたなー。

あれって別に珍しい事でも何でもなかったんですね。。。

ランダムな値で重複しなさそうーと思ってても、かぶる事って結構あるよね。

色々作ってたり使ってたりする中で、確かにそんな状況に出会う事はあったような気がする。

確率的な数値って、直観と反する事ってありますよね。

例えば、ドロップ率1000分の1の場合で、

実際に1000回倒して最低でも一つ出る確率は約63%だったり。

結構出ない。。。

↑の話って数式で考えると

(1 - 1/n)のn乗じゃないですか。

この形って↓じゃん。

sample

↑のnが大きくなればなるほど、「0.36」辺りに収束すると。

↑のeってネイピア数っていうんですが、

だから、確率関連の数式には良く出てくるのかーって

意味も分からず納得してた記憶がある。

だから何だって話なんですが。

Python ソート色々(オブジェクト、辞書のリストのソート)

公式のソートHowTo

色々と。

単一項目でのソートはまー良いんだけど、

複数項目で、昇順/降順を個別指定してのソートは

サンプルとかあまり見なかったので↓の感じにした。

simple_list = [5,10,1,4,8,0]
tuple_list = [(3,'b'),(10,'x'),(5,'z')]

class TestItem:
    def __init__(self, **kwargs):
        self.id = kwargs["id"]
        self.name = kwargs["name"]
        self.val = kwargs["val"]
    def __repr__(self):
        return repr((self.id, self.name, self.val))

obj_list = []
obj_list.append(TestItem(id=10,name="y",val=1024))
obj_list.append(TestItem(id=1,name="z",val=256))
obj_list.append(TestItem(id=5,name="x",val=128))
obj_list.append(TestItem(id=101,name="y",val=512))
obj_list.append(TestItem(id=102,name="y",val=64))

dict_list = []
dict_list.append({"id":10, "name":"y", "val":1024})
dict_list.append({"id":1, "name":"z", "val":256})
dict_list.append({"id":5, "name":"x", "val":128})
dict_list.append({"id":101, "name":"y", "val":512})
dict_list.append({"id":102, "name":"y", "val":64})


from operator import attrgetter


if __name__ == "__main__":
    # 普通にソート
    print(sorted(simple_list))
    # → [0, 1, 4, 5, 8, 10]

    # 降順にソート
    print(sorted(simple_list, reverse=True))
    # → [10, 8, 5, 4, 1, 0]

    # タプルの配列のソート(タプルの最初の項目でソート)
    print(sorted(tuple_list))
    # → [(3, 'b'), (5, 'z'), (10, 'x')]

    # タプルの配列の降順でソート(タプルの指定の項目でソート)
    print(sorted(tuple_list, key=lambda x: x[1], reverse=True))
    # → [(5, 'z'), (10, 'x'), (3, 'b')]

    # オブジェクトの配列のソート
    print(sorted(obj_list, key=lambda x: x.name))
    # → [(5, 'x', 128), (10, 'y', 1024), (101, 'y', 512), (102, 'y', 64), (1, 'z', 256)]
    
    # オブジェクトの配列のソート(attrgetter使用)
    print(sorted(obj_list, key=attrgetter("name")))
    # → [(5, 'x', 128), (10, 'y', 1024), (101, 'y', 512), (102, 'y', 64), (1, 'z', 256)]

    # 辞書の配列のソート
    print(sorted(dict_list, key=lambda x: x["name"]))
    # → [{'id': 5, 'name': 'x', 'val': 128}, {'id': 10, 'name': 'y', 'val': 1024}, {'id': 101, 'name': 'y', 'val': 512}, {'id': 102, 'name': 'y', 'val': 64}, {'id': 1, 'name': 'z', 'val': 256}]

    # ここから複合ソート

    # 複合ソート用関数
    def multisort(xs, specs, fkey):
        for key, reverse in reversed(specs):
            xs.sort(key=fkey(key), reverse=reverse)
        return xs
    
    # key関数を作る高階関数(オブジェクト用)
    def fobj(key):
        return attrgetter(key)

    # key関数を作る高階関数(dict用)
    def fdict(key):
        return lambda x: x[key]

    # オブジェクトの配列の複合ソート
    print(multisort(obj_list, [('name', False), ('val', False)], fobj))
    # → [(5, 'x', 128), (102, 'y', 64), (101, 'y', 512), (10, 'y', 1024), (1, 'z', 256)]

    # 辞書の配列の複合ソート
    print(multisort(dict_list, [('name', False), ('val', False)], fdict))
    # → [{'id': 5, 'name': 'x', 'val': 128}, {'id': 102, 'name': 'y', 'val': 64}, {'id': 101, 'name': 'y', 'val': 512}, {'id': 10, 'name': 'y', 'val': 1024}, {'id': 1, 'name': 'z', 'val': 256}]

で、Pythonのソートは安定(同一値の場合に元の並び順のままになる)なのを

保証してるみたいなので、上のロジックで複数キーのソートが出来るとさ。。。

「Computer Programming as an Art」機械翻訳

クヌースのチューリング賞受賞時の講演の論文?

日本語訳が見つからなかったので、とりあえず適当にGoogle翻訳でつけた。

メモ用に挙げとく。後で校正する。

訳付き

原本

MVCでトランザクションをどこに持たせるか

今更MVCの話。

結局MVCって役割の分離って事じゃないですか。

WebアプリでMVC(厳密に言うと原書のMVCではないけども)っぽく作る場合。

色んなサンプルを見てるとMVCのCにトランザクションのBeginとEndがあったりする。

システムのアーキテクチャ上、コントローラでトランザクション制御するっていうモノももちろんあると思うけど、

それしかないのもどうかなと思うのと、微妙な気がしてるので少し書いておく。

まず、Contorlerとは何をするべきなのかって事。

Webアプリだと、ココはやっぱり何のビジネスロジックを呼び出すかっていう部分と、

認証周りの処理、セッション周りの処理、あとはリクエストの内容を解析して、

ビジネスロジックに渡すイベントオブジェクト的なものを作る処理辺りだと思うのです。

で、良くあるトランザクションの処理で言われてるところが、

AっていうDBを更新する処理があって、その後にBっていう他のデータを更新する処理があって、

その2つの処理を関連付けて一つのトランザクションとして扱いたい

みたいなものがある。

その制御をするためにはモデルを呼ぶコントローラでやらなきゃ!

って感じなのかな~と。

そこに違和感を感じるわけです。

処理を関連付けるとか処理する順番に依存してるとかとか。

それに意味があるなら、それはビジネスロジックだよね?

という事でトランザクションの制御はコントローラではやらない方がいいと思うのです。

Contorlerに入れた時点で、HTTPのリクエスト渡さないと他から使えないじゃない?

モデル側でファサード的なクラスを用意しておけばいいように思うわけです。

MVCってModelViewContorlerに分ける「方向性」で組むと思うんですが、

分割の仕方は3つである必要はないと思うんですよ。

大事なのは役割の分割であって、3つという数に意味はないように思うのです。

なので、Controlerに持たせるのは何か微妙に思う。

あと、結構致命的だと思ってるのは、テスト書き辛くない?

Webアプリのコントローラって認証とセッションにほぼ結合してると思うので、

その辺の調整をテストコード側でやらないと呼ぶの無理じゃん?

テスト書きヅラ!!みたいな。

という事で、MVCを元に構成したWebアプリでトランザクション処理する時は

コントローラでやるべきではないと思うのでした。。。

問合せ