くりーむわーかー

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

2019年06月

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()使ってるんだろ?

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

DjangoとCeleryのジョブキューについて

Pythonでジョブキュー的に処理を外に投げたい場合に

Django+Celeryが良く見るやつ。

公式の通りにやれば特に問題なく動く。

で、一つ気になったところ。

これって、Djangoのアプリの中で下の感じで処理を投げるじゃないですか。

def aaction(request):
    res = some_task.delay()
    return HttpResponse(f"Delay Task!!!")

そしたら、Workerサーバというかサービスが↑でジョブキューに入った

処理依頼を処理する流れじゃないですか。

で、その時のWorkerプロセスの動きが、どう見ても、毎回Djangoアプリを初期起動してるんだよね。

そういうもん?バッチ処理的に使うなら、起動時間は全体の処理時間からみて

問題にならないって事なんかな?

2~3秒かかる程度の処理は投げない方が良い感じ?

それなりに大きいアプリだと起動処理もそこそこオーバーヘッドあると思うんだけど。。。

多分中で、django.setup()してると思うんだけど、

どうなんでしょ。何となく、起動時にsetup()はやって使いまわして欲しいのですが。。。

つか、それやったら、Djnagoの普通のWebサーバだろみたいな感じかしらね。

何とも釈然としないモヤモヤ感。

何かいいやり方ないかなー。もうちょい検討。。。

Python Djangoをローカルのスクリプトから読み込んで使う

Djangoのアプリの中でゴニョゴニョするんじゃなく、

別のスクリプトからDjangoアプリを呼び出して操作したい場合。

テストとかで。

公式的にはこの辺。

で、現状ちゃんと動くのは↓

import os
import sys
import django

#sys.path.append('/home/hogeuser/sandbox/mysite')
sys.path.append(os.getcwd())
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mysite.settings')
django.setup()

#↑の後ならモデルとか読み込んで使える
from myapp import models

a = SampleTable()
a.save()

なんだけど、環境変数とか使うのどうにかならないかなーと思い、試してみた。

#公式に乗ってるやつ ⇒ これは無理
import django
from django.conf import settings
import mysite.settings as mysettings
settings.configure(default_settings=mysettings, DEBUG=True)
django.setup()

# 下のエラーが出る
# AttributeError: module 'mysite.settings' has no attribute 'LOGGING_CONFIG'

どうもsettingsを入れるだけだと、djangoのデフォルトのsettingsを丸ごと上書きしてるらしく、

必要な定義が消えちゃうっぽい。

やるなら、自分のsettingsにDjangoのデフォルトのsettingsを全部書かないとダメっぽ。

そして、それがかなり量があるのでしんどい。

つか、公式でもデフォルトはdjangoでやってるから原則変えるんじゃねー

みたいな感じっぽい。

で、次に試したのは↓

#これは動くけど、自分のアプリが読み込まれてない
import django
from django.conf import settings
from django.conf import global_settings
settings.configure(default_settings=global_settings, DEBUG=True)
django.setup()

Djangoのデフォルトを読み込ませてみたらいいんじゃないか的な。

これだとうまくいくけど、自分のINSTALL_APPSが読み込まれてないので、色々ダメ。

まー当たり前か。

という事で、デフォルトは上書きせずにマージするようなやり方がないか調べてたけど、

良く分かりませんでした。。。

どうも環境変数に入れる以外に良い方法が出回ってなさそうですね。

つか、manage.pyの中でこの方法でやってるからこれしかないのかしらね。

誰かいい方法知ってたら教えてほしい。

Python ディスクリプタについて

ディスクリプタの使い方を色々考えてて、挙動が面白かったので書いておこう。

てか、ディスクリプタって何で使えばいいんだろうか。

何かに使えそうな感じはするんだけど、具体的に何使うかは何とも・・・。

オブジェクトに値入れる時のバリデーションを共通化とかかな?

とりあえず、公式はこの辺

で、公式に

クラスへ束縛すると、A.x は呼び出し A.__dict__['x'].__get__(None, A) に変換されます。

って書いてあったので、試しに、、、

class DescriptorA:
    def __set_name__(self,owner,name):
        self._name = name

    def __get__(self, instance, owner):
        return owner.a

class MyClassA:
    a = DescriptorA()

ってやってみたら、案の定、無限ループして落ちた。つか、Pythonのエンジン側で再帰の制限かかってたのね。初めて知った。

  [Previous line repeated 496 more times]
RecursionError: maximum recursion depth exceeded

ふむふむ。アクセスメソッド的に呼ばれるインターフェースが決まってて、それを定義したクラスがディスクリプタって感じでしょか。

で、公式に乗ってるサンプルは下記なんだけど、

class RevealAccess(object):
    """A data descriptor that sets and returns values
       normally and prints a message logging their access.
    """

    def __init__(self, initval=None, name='var'):
        self.val = initval
        self.name = name

    def __get__(self, obj, objtype):
        print('Retrieving', self.name)
        return self.val

    def __set__(self, obj, val):
        print('Updating', self.name)
        self.val = val

class MyClass(object):
...     x = RevealAccess(10, 'var "x"')
...     y = 5

これって、値の本体はMyClassじゃなくて、RevealAccess側のインスタンスだよね?

なんかMyClassのクラス変数でもインスタンス変数でも無いように思うのですが、こういう使い方が良いの?

てか、むしろこれやると、


class DescriptorB:
    def __init__(self,value):
        self.value = value

    def __set_name__(self,owner,name):
        self._name = name

    def __get__(self, instance, owner):
        return self.value

    def __set__(self, instance, value):
        self.value = value

class MyClassA:
    a = DescriptorA(10)
    b = 100

if __name__ == "__main__":
    x = MyClassB()
    y = MyClassB()

    print(f"before x.a : {x.a}")
    print(f"before y.a : {y.a}")
    y.a = 20
    print("set : y.a = 20")
    print(f"after x.a : {x.a}!!!!")
    print(f"after y.a : {y.a}")

    print(f"before x.b : {x.b}")
    print(f"before y.b : {y.b}")
    y.b = 200
    print("set : y.b = 200")
    print(f"after x.b : {x.b}")
    print(f"after y.b : {y.b}")

↓↓↓↓↓↓↓↓

before x.a : 10
before y.a : 10
set : y.a = 20
after x.a : 20!!!!
after y.a : 20

before x.b : 100
before y.b : 100
set : y.b = 200
after x.b : 100
after y.b : 200

って感じで、なんちゃってシングルトンのような超グローバル変数的な何かになる。

意図せずやっちゃったら相当ヤバイけど、意図的に何かに使えそうな気もする。

で、あと、色々見てると↓の感じでインスタンス変数扱いにしてるのを見る。

class DescriptorC:

    def __set_name__(self,owner,name):
        self._name = name

    def __get__(self, instance, owner):
        return instance.__dict__[self._name]

    def __set__(self, instance, value):
        instance.__dict__[self._name] = value

class MyClassC:
    a = DescriptorC()

if __name__ == "__main__":
    x = MyClassC()
    x.a = 10
    print(x.a)

↓↓↓↓↓

10

で、インスタンス変数へのアクセスっぽいんだけど、これって、値の代入するまでインスタンスの__dict__に対象の名前いないから、代入する前にアクセスすると落ちるよね?

if __name__ == "__main__":
    x = MyClassC()
    print(x.a)#これは落る
    x.a = 10
    print(x.a)#こっちは落ちない
↓↓↓↓↓
return instance.__dict__[self._name]でkeyerror

あと、公式だと↓で書いてあるので、

オブジェクトインスタンスへ束縛すると、a.x は呼び出し type(a).__dict__['x'].__get__(a, type(a))

クラスへ束縛すると、A.x は呼び出しA.__dict__['x'].__get__(None, A)

クラス変数として呼ぶと

if __name__ == "__main__":
    x = MyClassC()
    x.a = 10
    print(x.a)
    print(MyClassC.a)#これでも__get__呼び出されてキーエラー

んー、扱いがムズイ。

基本的にはインスタンス変数として扱いたいんだと思うので、↓の感じがいいのかしら。

class DescriptorD:

    def __set_name__(self,owner,name):
        self._name = name

    def __get__(self, instance, owner):
        if instance is None:
            return "申し訳ないがClassはNG"

        return instance.__dict__[self._name]

    def __set__(self, instance, value):
        instance.__dict__[self._name] = value

class MyClassD:
    a = DescriptorD()
    def __init__(self):
        self.a = ""

if __name__ == "__main__":
    x = MyClassD()
    print(x.a)
    x.a = "init"
    print(x.a)
    print(MyClassD.a)

どうなんでしょ。個人的にはこれがいいと思うんだけど。それか、__get__の中でkeyが無かったら何かしらの初期値入れて、初期化するように組むか。

あと、公式の引数がそうなってるからだと思うのですが、色々見るサンプルって、下の感じ。

object.__get__(self, instance, owner)

このownerってクラスなんだけど、分りにくくない?

実際、Djangoのmodelsのソースでもディスクリプタ使われてるんですけど、

下の名前で使ってる。

def __get__(self, instance, cls=None):

clsの方が分かりやすいと思うんだけどなー。

Python 内包表記まとめ

完全にメモ。

import random,string,time
from operator import itemgetter, attrgetter

def randomname(n):
   return ''.join(random.choices(string.ascii_letters + string.digits, k=n))

class SampleClass:
    def __init__(self,name,val,hoge):
        self.name = name
        self.val = val
        self.hoge = hoge

# 単純なList
numl = [random.randint(1, 1000) for i in range(100)]
selected = [v for v in numl if v < 500]
print(selected)

# 辞書
dicl = [{"key": randomname(5),"val":random.randint(1, 1000)} for i in range(100)]
selected = [v for v in dicl if v["val"] < 500]
print(selected)

# 辞書から特定要素の配列を作る
dicl = [{"key": randomname(5),"val":random.randint(1, 1000)} for i in range(100)]
selected = [v["key"] for v in dicl if v["val"] < 500]
print(selected)

# タプル
tupl = [(randomname(5),random.randint(1, 100), random.randint(200, 300) ,) for i in range(100)]
selected = [v for v in tupl if v[1] < 50]
print(selected)

# タプルのリストから部分的な要素にしたタプルのリストにする
tupl = [(randomname(5),random.randint(1, 100), random.randint(200, 300) ,) for i in range(100)]
selected = [(v[0],v[2],) for v in tupl if v[1] < 50]
print(selected)

# オブジェクトから特定フィールドのタプルにしつつ、リストにする
objl = [SampleClass(randomname(5),random.randint(1, 100),random.randint(200, 500)) for i in range(100)]
selected = [attrgetter("name","hoge")(v) for v in objl if v.val < 50]
print(selected)

Python Listのソートまとめ

list.sort()と sorted(list)の 2 つ。

まずは → を読むべし。https://docs.python.org/ja/3/howto/sorting.html

オブジェクトの配列をソートするとかする場合はkey関数を使う。

性能とか

import random
import time

time_sta = time.perf_counter()
for i in range(10000):
    numl = [random.randint(1, 1000) for i in range(100)]
    numl.sort()
time_end = time.perf_counter()
tspan = time_end- time_sta
print(tspan)

time_sta = time.perf_counter()
for i in range(10000):
    numl = [random.randint(1, 1000) for i in range(100)]
    sorted(numl)
time_end = time.perf_counter()
tspan = time_end- time_sta
print(tspan)

# どっちも大差無
3.690590940999982
3.6823944669999946

昇順/降順

numl = [random.randint(1, 1000) for i in range(100)]
print(numl.sort(reverse=True))
print(sorted(numl, reverse=True))

両者の違い

numl = [random.randint(1, 1000) for i in range(10)]
print(numl)
print(numl.sort())
print(numl)

[746, 897, 3, 989, 2, 216, 666, 852, 387, 409]
None
[2, 3, 216, 387, 409, 666, 746, 852, 897, 989]

numl = [random.randint(1, 1000) for i in range(10)]
print(numl)
print(sorted(numl))
print(numl)

[931, 793, 220, 503, 827, 961, 114, 530, 30, 528]
[30, 114, 220, 503, 528, 530, 793, 827, 931, 961]
[931, 793, 220, 503, 827, 961, 114, 530, 30, 528]

`list.sort()`は破壊的

`sorted(list)`は非破壊的

で、基本的には`sorted()`を使った方が良さそう。

Key 関数の使い方と性能

使い方
import random,string,time
from operator import itemgetter, attrgetter

dicl = [{"key": randomname(5),"val":random.randint(1, 1000)} for i in range(5)]

print( sorted(dicl,key=lambda x:x["key"]) )
print( sorted(dicl,key=lambda x:x["val"]) )

print( sorted(dicl,key=itemgetter("key")) )
print( sorted(dicl,key=itemgetter("val")) )
性能
import random,string,time
from operator import itemgetter, attrgetter

dicl = [{"key": randomname(5),"val":random.randint(1, 1000)} for i in range(1000)]
time_sta = time.perf_counter()
for i in range(10000):
    sorted(dicl,key=lambda x:x["key"])
time_end = time.perf_counter()
tspan = time_end- time_sta
print(tspan)

time_sta = time.perf_counter()
for i in range(10000):
    sorted(dicl,key=itemgetter("key"))
time_end = time.perf_counter()
tspan = time_end- time_sta
print(tspan)

7.373934460000328
6.795032753000214

`itemgetter` 使いましょう。

色んなデータ構造でソート

# 単純なList
numl = [random.randint(1, 1000) for i in range(5)]
print(sorted(numl))
print(sorted(numl,reverse=True))

# 辞書
dicl = [{"key": randomname(5),"val":random.randint(1, 1000)} for i in range(5)]
print(sorted(dicl,key=itemgetter("key")))
print(sorted(dicl,key=itemgetter("val"),reverse=True))
print(sorted(dicl,key=itemgetter("val","key")))# 複数キーのソート

# タプル
tupl = [(randomname(5),random.randint(1, 100), random.randint(200, 300) ,) for i in range(5)]
print(sorted(tupl,key=itemgetter(0)))
print(sorted(tupl,key=itemgetter(2),reverse=True))
print(sorted(tupl,key=itemgetter(2,0)))# 複数キーのソート

# オブジェクト
objl = [SampleClass(randomname(5),random.randint(1, 100)) for i in range(5)]
print(sorted(objl,key=attrgetter("name")))
print(sorted(objl,key=attrgetter("val"),reverse=True))
print(sorted(objl,key=attrgetter("val","name")))# 複数キーのソート

class SampleClass:
    def __init__(self,name,val):
        self.name = name
        self.val = val
問合せ