くりーむわーかー

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

Python

Python Djangoで単体テストの結果をマージ

Djangoで単体テストやってる時の話。規模が大きくなると実行対象分割して実行させたくなる。分割の仕方は色々あると思うので割愛。

で、分割実行した場合って結果のcovrageとかも分割されるので、結果をどうにかマージしたい。

jenkinsなんかに表示させてる場合、xunit形式の結果と.coverageが必要になる。

これをどうマージするのかという話。


coverage

Coverage.pyというのがあり、それを使う。というかDjangoの中でも使ってる模様。

普通の単体テストは「python manage.py test」みたいに実行すると思うのですが、

色々やりにくいので、Coverage.pyを通して実行するように変える。↓の感じ。

coverage run --source='.' manage.py test app/tests

で、これだとcoverageのデータファイルが全部「.coverage」になっちゃうので、ファイル名を変えるために下記にする。

COVERAGE_FILE=.coverage_datafile coverage run --source='.' manage.py test app/tests

環境変数の「COVERAGE_FILE」にファイル名指定しておいてから実行するらしい。

そうすると分割したテスト毎にファイル名固定出来るのであとはそれを最後に下記でマージする。

coverage combine .coverage_*

「.coverage_」でファイルを全部指定みたいな。上を実行すると「.coverage」ってデータファイルにまとまるので、あとは、「coverage html」とか「coverage xml」何かで必要なレポート形式に変換する。


xunit

xunitも同様にマージが必要。とりあえず、テストの実行時に下記のオプション指定して結果のファイル名を実行毎に指定してあげる。

manage.py test app/tests/target --with-xunit --xunit-file nose_result_xxx.xml

そしたら、こっちも実行毎にファイルが出来るので、これをマージする。

で、pythonでこれをマージするツール無いかなーって探してみたのですが、下記しかない。

https://pypi.org/project/xunitmerge/

ただ、このツール開発止まってるぽくて更新6年くらい止まってるんですよね。。。

ついでに、python3系で出来ない書き方してるようでそのままだと動かない。

下のプルリクの修正が必要みたい。

https://github.com/miki725/xunitmerge/pull/9

本体に取り込まれる事は無さそうな気がするので、自分でリポジトリ作ってそっち直して、そっちからPIPでインストールする。

GitHubとかGitLabとかリモートリポジトリからpipインストールする場合は↓の感じ。

※別に直で実行してもOK。

pip install  -b release git+https://github.com/***/***/xunitmerge

実行できるようにしたら、下記でマージする。

xunitmerge nosetests_*.xml nosetests.xml

「nosetests_」で始まるファイルを「nosetests.xml」にマージするみたいな。

あとはこれらをjenkinsなりに食わせればOKという寸法です。


最終的にDjangoのtest実行する時のコマンドはもろもろオプションとかつけた感じにすると下記の感じ。

COVERAGE_FILE=.coverage_datafile coverage run --source='.' manage.py test app/tests --settings=dapp.settings_hoge.py --keepdb --with-xunit --xunit-file nosetests_xxx.xml

Python import thisの裏

import thisは色んな人が解説書いてるので少し違った個人の視点をば。

import thisのソースってこんな感じ。

s = """Gur Mra bs Clguba, ol Gvz Crgref

Ornhgvshy vf orggre guna htyl.
Rkcyvpvg vf orggre guna vzcyvpvg.
Fvzcyr vf orggre guna pbzcyrk.
Pbzcyrk vf orggre guna pbzcyvpngrq.
Syng vf orggre guna arfgrq.
Fcnefr vf orggre guna qrafr.
Ernqnovyvgl pbhagf.
Fcrpvny pnfrf nera'g fcrpvny rabhtu gb oernx gur ehyrf.
Nygubhtu cenpgvpnyvgl orngf chevgl.
Reebef fubhyq arire cnff fvyragyl.
Hayrff rkcyvpvgyl fvyraprq.
Va gur snpr bs nzovthvgl, ershfr gur grzcgngvba gb thrff.
Gurer fubhyq or bar-- naq cersrenoyl bayl bar --boivbhf jnl gb qb vg.
Nygubhtu gung jnl znl abg or boivbhf ng svefg hayrff lbh'er Qhgpu.
Abj vf orggre guna arire.
Nygubhtu arire vf bsgra orggre guna *evtug* abj.
Vs gur vzcyrzragngvba vf uneq gb rkcynva, vg'f n onq vqrn.
Vs gur vzcyrzragngvba vf rnfl gb rkcynva, vg znl or n tbbq vqrn.
Anzrfcnprf ner bar ubaxvat terng vqrn -- yrg'f qb zber bs gubfr!"""

d = {}
for c in (65, 97):
    for i in range(26):
        d[chr(i+c)] = chr((i+13) % 26 + c)

print("".join([d.get(c, c) for c in s]))

文字列表示してるだけなのですが、カエサル式(シーザー式)暗号っていうのやってます。

65がasciiコードでいう「A」で97が「a」。でそれぞれ26文字分繰り返して13文字分ずらした文字に直して出力。みたいな。


こういうの見ると「遊び心」があるー。って感じするじゃん?

何か洒落てる感じするじゃないですか。

「遊び心」が大事だよねって思うじゃないですか。

で、遊ぶってすごいなと思うわけです。

例えば将棋とかチェスとか、野球とかサッカーとかバスケみたいなスポーツでもいいし、大工さんだったりF1だったり、別にゲームでもいい、例えば地球防衛軍とかバイオハザードとか。

何でもそうだと思うのですが、本気で遊べるためには、その何かに精通してなきゃ無理だと思うんですよ。

それなりに精通してないと遊ぶ余裕が全くないし、動かすだけで精一杯。

遊び心が出せるって事はそれに精通してるって事。

そのくらい知識とか経験とかちゃんと身に着けなよって言われた気がしたのです。

まー気のせいだ。。。

Pythonのクラス変数を定数(再代入禁止の変数)として扱う - 名前空間を分けたい

Pythonには他の言語でいう定数がない。

慣習的に定数扱いにしたい場合は大文字で書くみたいなのはある。

FOO = 100

みたいな。。。

ちんまりしたアプリだったら別に上記のくらいのルールでもいいかもだけど、

そこそこ大きいアプリになって色んな開発者が長期に渡って触る可能性がある場合。

定数を定数として扱わなくなる危険性があるので、ちゃんと再代入は禁止できるようにしたい。

で、これをやるのによく見るのって下の感じ。

# 「const.py」ってファイルで作成

class _const:
    def __setattr__(self, name, value):
        if name in self.__dict__:
            # 定義されたら例外出す
            raise TypeError(f'Can\'t rebind const ({name})')
        self.__dict__[name] = value

import sys
sys.modules["const"]=_const()

# ↑を作ったら↓

import const

const.FOO = 'hoge'
const.FOO = 'fuga' # 例外でる

これで再代入を禁止した定数っぽいものが作れると。参考元


ただ、これって「const」クラスの「インスタンス変数」として突っ込んでるだけ。ってそれがダメなわけじゃないですが、大量に定義したい時なんかはちょっと・・・。

あと、試してないのですが、名前空間一つになっちゃうと思うので、色んなPGで使うと変数名かぶったりした時にやられたりしないのかしら?

という事で、定数をある程度まとまった役割(名前空間)で分けたい場合に例えば、、、

class Moge:
   FOO = 100

# ↑こういう風にまとめて、
# ↓こういう風に使いたい

do_something(Moge.FOO)

って感じにしたいのですが、前述のやり方だと

class Moge:
   const.FOO = 100

みたいになっちゃうので無理。

何かやり方ないかなと思ってたら、PythonのClass定義は、それ自体もインスタンスだったなと思い出しまして、こんな風にしてみたら意外と出来た。


# クラス定義そのものに対してのsetter制御用メタクラス
class ConstMeta(type):
    def __setattr__(self, name, value):
        if name in self.__dict__:
            raise TypeError(f'Can\'t rebind const ({name})')
        else:
            self.__setattr__(name, value)

# 定数定義
class MyConstClass(metaclass=ConstMeta):
    X = 10
    Y = 20

MyConstClass.X # 10
MyConstClass.X = 30 # 例外
MyConstClass.Z = 40 # これは別の例外

これで定数を役割なんかでまとめた分類が出来るという寸法です。

Metaクラスってクラス定義に対しての特殊メソッドを定義出来るので、

↑の感じでクラス変数(属性)へのsetterをねじ込んだ感じ。

ただ、この場合は本当に定数だけのクラスじゃないとダメだと思う。

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

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

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

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やるだけなら十分。。。

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

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

バースデーパラドックス

バースデーパラドックス(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のソートは安定(同一値の場合に元の並び順のままになる)なのを

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

禅とプログラミング

最近Python触ってるんですが、

Pythonには「The Zen of Python」っていう設計思想みたいなものがある。

で、禅って割とシステムとか作ってる人に受けがいいのかなーと思った。

スティーブ・ジョブスなんかも禅が好きだったらしいですよね。

Googleも社内研修みたいなのに、禅の瞑想入門講座みたいなのがあって結構人気があるとか。

エンジニアじゃないけど、イチローも禅が好きみたいな話は聞いたことある。

で、禅って何のかなーって考えてみた。

スティーブ・ジョブズの禅の話とかはコノのサイトにちょっと書いてある。

で、その中で、

「彼はマーケティングを一切しなかった。座禅によって自分の中に下りていき、自分が本当に望むものを徹底的に見ようとした。自分の深いところから来るものを作るから、製品は相手の深いところを揺さぶる力を持っていた。自分は一体何を望むのか。それを探るのが彼の究極のマーケティング・リサーチだったのでしょう」

ジョブズが2005年に米スタンフォード大学の卒業式で行った有名な演説がある。「私は毎朝、鏡の中の自分に向かって、『今日が人生最後の日だったとしたら、今日の予定をやりたいと思うだろうか』と問いかける。『ノー』の日が続いたら、何かを変えなければいけない」

「自分に日々問いかけ、その時に本当にしたいことをしなければいけないと言っている。普通はそう思ってもなかなかできない。だって空気を読んで、皆に好かれる生き方をした方が楽だから。しかし、ジョブズは違った。深く下りていき、普段のモノの見方とは違う、もう一つの視点をキープしようとした。そのために坐禅を使ったのでしょう」

という事らしい。。。

仏教的には悟りの体験って事なんでしょうか。

wikiを引用すると↓。

悟りというものは自分の心で自分の心を確認し、自分の心で自分の心を理解するものである。他人に頼って何かを明らかにするとか、自分以外の何かを利用して体得するようなものではない。

悟るためには何よりもまず坐禅の実践によって自分自身と向き合うことが肝要である。

自分の中にあるものを見つめ直す的な。

で、何かを得ようとするんじゃなくて、要らないものを捨てていって、

最後まで残ってるものが大事なコトみたいに考えるのかしら。

Simple is Bestっていうしね。

考えてみるとクラス設計みたいな事にも通ずるものがあるのかなーと。

例えばオブジェクト指向でいうと継承。

継承って、良くある説明だと「犬⇒哺乳類⇒動物」みたいな。

で、親クラスの性質を引き継ぐみたいな感じじゃない?

じゃー犬の性質、動物の性質ってなんなんでしょ?

その概念に必要な性質ってなんだろうか。って事を考える必要があるわけで。

概念を構成するもの、概念に共通しているコトなんかを考えないといけないよね。

そういうものを考えるアプローチとして例えば、

「is-a」、「has-a」っていう関係性を考えるってものがあるじゃない。

is-aは分類的な思考、has-aは分割的な思考ですよね。

分類できるって事は何かしらの共通項があるって事。

継承を考える時はそういう概念的な共通項を見出して、

この概念であるにはこうなってなきゃいけない的にプログラムを構成すると思うのです。

この共通項を見出すって事が、いわゆる本質を探るって事なんでしょう。きっと。

で、共通項を探るためには、不要な要素は全部そぎ落としていって、

最後に残っていったものが共通項みたいな見方もできるじゃない。

なので、禅にも通ずるのかなーなんて思った訳です。

他には、禅はある意味で自分本位の考え方かなと。

他の人がどう考えてるとか、ユーザのニーズがーとかそういう事じゃなくて、

自分がしたい事、思ってる事は何のかってコトを突き詰めていく感じ。

そういう精神性が例えば、The Zen of Pythonなんかだと、

Now is better than never.

やらないよりは今やれ

COBOL作ったホッパーさんが言うところの

It's easier to ask forgiveness than it is to get permission.

許可を得るよりも許しを求める方が簡単です。

って事なんだろうなーと思った次第です。

で、「ニーズに応える」よりも「ニーズを作り出す」っていう事の方が、

インパクトとしてはデカいと思うんですよ。

「ニーズを作り出す」には周りをうかがってても出てこないんじゃないかなと。

自分がしたいと思った事をやってみることで「ニーズが生まれる」じゃないかと思った次第。

もちろん、自分がしたいと思った事が他の人のニーズになる事はきっと稀でしょう。

上手くはまったのが、スティーブ・ジョブズなんだろうなと。

で、自分のやりたい事をやるのが良いんだ、みたいな論調に記事の中とかでは

言ってたりすると思うんですが、多分それは禅ではないよね。

そういう記事に書かれてる事、スティーブ・ジョブズがやってた事、

そういうのを意識した時点で禅じゃないんだろうなと。

あくまでも自分の中で生まれたものが禅なんでしょう。

ジョブズみたいに、やりたい事をやった結果でニーズを生みだす人もいれば、

そういうニーズに応える事にやりがいを感じる人もいるでしょうし。

その人によりけりですよね。やっぱ。

こういう人それぞれの自分の中で大事なコトっていうを尊重してくのが

禅であって、多様性みたいな事なんだろうなと思った次第です。

Python typeでクラス定義を書き換える

なんのこっちゃって感じ。rest_frameworkのソース読んでて、

あー、これはこういう事に使うのかってピンと来たので残しておく。

DjangoのModelもそうなんだけど、シリアライザを作る時に

class CommentSerializer(serializers.Serializer):
    email = serializers.EmailField()
    content = serializers.CharField(max_length=200)
    created = serializers.DateTimeField()

こんな感じで、「クラス変数」にモデルの定義を入れてる。

で、クラス変数ってstaticだから、インスタンス化して使おうが、

結局全インスタンスで共有されてる変数。

でも、DjangoもRestFrameworkもちゃんとインスタンス変数になってる。

なんでかなーって不思議だったんですが、ソース読んでると、

metaclassの定義使って、クラス定義読み込まれる時に、クラス変数を潰して、

インスタンス変数作ってるんですね。

RestFrameworkのソースだと↓の部分。

class SerializerMetaclass(type):
    """
    This metaclass sets a dictionary named `_declared_fields` on the class.
    Any instances of `Field` included as attributes on either the class
    or on any of its superclasses will be include in the
    `_declared_fields` dictionary.
    """

    @classmethod
    def _get_declared_fields(cls, bases, attrs):
        fields = [(field_name, attrs.pop(field_name))
                  for field_name, obj in list(attrs.items())
                  if isinstance(obj, Field)]
        fields.sort(key=lambda x: x[1]._creation_counter)

        # If this class is subclassing another Serializer, add that Serializer's
        # fields.  Note that we loop over the bases in *reverse*. This is necessary
        # in order to maintain the correct order of fields.
        for base in reversed(bases):
            if hasattr(base, '_declared_fields'):
                fields = [
                    (field_name, obj) for field_name, obj
                    in base._declared_fields.items()
                    if field_name not in attrs
                ] + fields

        return OrderedDict(fields)

    def __new__(cls, name, bases, attrs):
        attrs['_declared_fields'] = cls._get_declared_fields(bases, attrs)
        return super().__new__(cls, name, bases, attrs)

で、Serializerの定義が↓

class Serializer(BaseSerializer, metaclass=SerializerMetaclass):

SerializerMetaclassの↓のところ

    @classmethod
    def _get_declared_fields(cls, bases, attrs):
        fields = [(field_name, attrs.pop(field_name))
                  for field_name, obj in list(attrs.items())
                  if isinstance(obj, Field)]
        fields.sort(key=lambda x: x[1]._creation_counter)

attrsにクラス定義でのクラス変数とか入ってて、これが最終的にクラス定義内に

展開されるっぽい。なので、attrs.popでクラス変数として消してる。

消すタイミングで、fieldsって変数に定義を退避してる感じ。

で、typeを継承してて、最後に「return super().__new__(cls, name, bases, attrs)」してる

という感じ。

なるほどー。ってなんか納得した。

pythonでメタプログラミングするならtype継承してクラス定義が作られてるって

知っとけみたいな話見てて「は?」って思ってたんだけど、

こう具体的なtypeの使い方見ると納得できますね。。。

で、↑のが何で便利なのかというと、MVCとMVVMとかで作ってる時って、

View層とのやり取りは専用のViewModel作ると思うんですよ。

バリデーションとか統一しやすいし。

で、ViewModelの定義する時のベースにこれがすごく使える。

一つ一つのフィールドのデータ型を個別に作れるので。

一つ賢くなりましたというお話。。。

Pythonの今やれ論

Pythonで「import this」ってやると設計思想(The Zen of Python)みたいなものが出るのは有名な話。

その中で「Now is better than never.」っていうのがある。

「やらないよりは今やれ」って話。

まーよく言われるコトなんですが、

ふと思い出してみると、COBOL作ったホッパーさんも同じコト言ってるなーと。

「It's easier to ask forgiveness than it is to get permission.」

「許可を得るよりも許しを求める方が簡単です。」

とりあえずやってみて、うまくいかなかったら謝ればいいじゃん的な。

PythonのThe Zen of Pythonの方はコード的な説明がされてたりもするけど、

これってPythonを開発してる人向けなモノの気がするので、

どっちかっていうと、「とりあえずやってみて反応を見るっていうのも有りだよ」って話だと思ってる。

会社で仕事してると、どうしても周りとか上司にお伺い立ててちゃう感じに

刷り込まれるのでホウレンソウとかさ。

まー組織としてはそれが健全なのかもしれないけど、

新しいモノみたいなのはそれだと出にくいよね。やっぱ。

で、管理職系の人らって「アイディアをー」とか「新規事業~」なんかが

やたら好きでわーわー言うのに、「え?アンタは何してんの(してきたの)?」って

感じの人ばっかなので、なんだかな~って。。。

周りは気にせず色々やりましょうって話にしかなんないけど、色々難しいよね。

あと、Pythonの方はこれに続くのがあって、

「Although never is often better than *right* now.」

「でも、今"すぐ"にやるよりはやらないほうがマシなことが多い。」

一言に「とりあえずやってみる」って言っても、考えなしにただやればいいってもんじゃないよね。

修正をしやすくしてみたり、色々吟味は必要でしょう。

その辺の折り合いは結構難しいですね。。。

問合せ