くりーむわーかー

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

Python

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.」

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

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

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

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

PythonでSingletonの実装に一言

PythonでSingleton(シングルトン)使いたくなったんで、

色々調べてみたんですが、見てく中でちょっと、ん?って感じた。

PythonにはC#とかで言う、privateがないので、いろいろ工夫が必要。ってのは分かる。

例えば、

# まずとりえず
class SingletonClass:
    __instance = None
    def __init__(self):
        self.val = "value"
    
    @classmethod
    def get_model(cls):
        if cls.__instance is None:
            cls.__instance = SingletonClass()
        return cls.__instance

if __name__ == "__main__":
    x = SingletonClass.get_model()
    y = SingletonClass.get_model()
    print(f"x = {id(x)} , y = {id(y)}")  # x = 140550875600936 , y = 140550875600936

get_modelを使ってる間は大丈夫。ただ、SingletonClass()の普通のコンストラクタに

制限かけてるわけじゃないので、1つである事を保証はしてない。

なので、まず__new__を使ってみる。

#__new__使う場合
class SingletonClass:
    __instance = None

    def __init__(self):
        print("__init__")
        self.val = "value"

    def __new__(cls):
        print("__new__")
        if cls.__instance is None:
            cls.__instance = super().__new__(cls)
        return cls.__instance
    
if __name__ == "__main__":
    x = SingletonClass()
    print(x.__dict__)
    y = SingletonClass()
    print(x.__dict__)

    print(f"x = {id(x)} , y = {id(y)}")

↓実行結果
__new__
__init__
{'val': 'value'}
__new__
__init__
{'val': 'value'}
x = 140367603169544 , y = 140367603169544

公式のドキュメント的には

__init__は、インスタンスが (__new__() によって) 生成された後、それが呼び出し元に返される前に呼び出されます。引数はクラスのコンストラクタ式に渡したものです。

__new__() が cls のインスタンスを返した場合、 __init__(self[, ...]) のようにしてインスタンスの __init__() が呼び出されます。このとき、 self は新たに生成されたインスタンスで、残りの引数は __new__() に渡された引数と同じになります。

__new__() が cls のインスタンスを返さない場合、インスタンスの __init__() メソッドは呼び出されません。

__new__() の主な目的は、変更不能な型 (int, str, tuple など) のサブクラスでインスタンス生成をカスタマイズすることにあります。また、クラス生成をカスタマイズするために、カスタムのメタクラスでよくオーバーライドされます。

って事らしいので、__new__でオブジェクトの実態が作られてるから、__new__の中で既にインスタンスがあるかどうか見る感じ。

で、この実装だと、

    x = SingletonClass()
    y = SingletonClass()

    x.val = "x-value"
    print(f"y.val = {y.val}")
    x.val = "y-value"
    print(f"x.val = {x.val}")


↓結果
y.val = x-value
x.val = y-value

みたいに書けるので、シングルトンを意識しないで使えて、値も共有出来て便利ー。みたいな感じ?

正直、「え?それってどうなん?」って思た。

つか、これってシングルトンっていうか、

ただのグローバル変数 だよね?

確かに、オブジェクトのIDは一致してるんだけど、上で行くと、2回目の__new__の中で

__init__呼び出されてるから、self.valの値変わっちゃうよね?↓の感じ。

    x = SingletonClass()
    x.val = "x-value"
    print(f"x.val = {x.val}")
    y = SingletonClass()
    print(f"x.val = {x.val}")

↓結果
x.val = x-value
__new__
__init__
x.val = value

んーと、これだいぶヤベー気がするのですが、こういうモンなの?

あと、シングルトン意識しなくていいとか、どうなんだろ。

シングルトンは意識して使って欲しいと自分は思う。

つか、シングルトンって何のために使うんだろ。

グローバル変数の変わり?それは違うよね?

オブジェクト指向自体、グローバル変数との闘いの果てに出来たものだと思うので、

そういう使い方は違うように思う。

シングルトンって、不変な何かのマスタとか設定とか、オブジェクト指向だと、

どうしてもインスタンスを毎回作る感じになってしまうので、

そういう不要なインスタンスを作らないでパフォーマンスとかメモリ効率を

上げるためのものだと思ってます。

ただ、Pythonだとやっぱりどうしてもオブジェクト内の値すらも、自由に書き換え出来ちゃうから、

こういうの強制させるものは作るの無理なんかな。。。

一人で作ってる時は正直どーでもいいんだけど、10人とか開発者がいると、

色々意識して欲しいので、とりあえずこんな感じにしてみた。

class SingletonClass:
    def __new__(cls):
        raise NotImplementedError("コンストラクタ直呼びは禁止。SingletonFactory.get_model()を使用。")

    @classmethod
    def __private_init__(cls, self):
        print("__init__")
        self.val = "value"
        return self

    @classmethod
    def __private_new__(cls):
        print("__new__")
        return cls.__private_init__(super().__new__(cls))

    def inst_method(self):
        return True

class SingletonFactory:

    __instance = None

    @classmethod
    def get_model(cls):
        if cls.__instance is None:
            cls.__instance = SingletonClass.__private_new__()
        return cls.__instance

完全に自前でコンストラクタ用意した感じ。

いちを、普通に__new__と__init__で作ったオブジェクトとdir()の結果は

一致してたから大丈夫だと思うんだけど、ちょっと心配。

ただ、まー不変な設定とかマスタをキャッシュ的に使いたいだけなのでこれでもいいか。

で、これだとスレッドセーフじゃないので、ロックをつける。

import threading

class SingletonFactory:

    __instance = None
    _lock = threading.Lock()

    @classmethod
    def get_model(cls):
        with cls._lock: 
            if cls.__instance is None:
                cls.__instance = SingletonClass.__private_new__()
        
        return cls.__instance

あと、起動中は不変なデータなハズなんだけど、もしかしたら、稼働中に

値の更新をしたくなるかもしれないので、いちを更新用のメソッドをつけておく。

最終系。。。

import threading

class SingletonClass:
    def __new__(cls):
        raise NotImplementedError("コンストラクタ直呼びは禁止。SingletonFactory.get_model()を使用。")

    @classmethod
    def __private_init__(cls, self):
        print("__init__")
        self.val = "value"
        return self

    @classmethod
    def __private_new__(cls):
        print("__new__")
        return cls.__private_init__(super().__new__(cls))

    def inst_method(self):
        return True

class SingletonFactory:

    __instance = None
    _lock = threading.Lock()

    @classmethod
    def get_model(cls):
        with cls._lock: 
            if cls.__instance is None:
                cls.__instance = SingletonClass.__private_new__()
        
        return cls.__instance

    @classmethod
    def recreate(cls):
        with cls._lock: 
            cls.__instance = SingletonClass.__private_new__()
        return cls.__instance

もちろん、Pythonのコードなので、正直、取得した後に値とかオブジェクトの内容を変更したりは

余裕で出来る。ただ、意識はしてくれるんじゃないだろうかと期待。

StackOverFlowだと、デコレータ使ったりして定義してるっぽいけど、どうなんでしょ。

サクッと作るならこの辺りかなーとか思いました。

この辺も参考になる。

ついでに、Djangoのソース読んでるとロックのところって下で書いてる。

_lock = threading.RLock()

なんで、RLock()使ってるんだろ?

理由が思いつかない。知ってる人誰か教えてください。

問合せ