くりーむわーかー

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

Python

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

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

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
問合せ