li re は 2を体現するもの かも
— 彼ノ∅有生 aka おかゆ (@oka_iu_tcan) January 22, 2017
PythonでUFCSの思案 ver0.001
色々と問題点しかない。たたき台ということで
# coding=utf-8 class UFCS: def __init__(self, value): self._value = value def __getattr__(self, name): return lambda *arg, **kwds: UFCS(eval(name)(self._value, *arg, **kwds)) def __repr__(self): return self._value UFCS(111).print() UFCS([1, 2, 3]).len().print()
threading の同期制御の概念的なメモ
17.1. threading — スレッドベースの並列処理 — Python 3.5.2 ドキュメント
Lock
まあロックはロックやろ。データの所有権みたいなもの
厳密にはデータの所有権ではなくて、諸々のデータを用いた行動の実行権か。諸々のデータを「一人占め」して安全に行動を実行できるための。
とはいえ、Lockの権利は他の人にぶん取られることがあるから、気が済むまで悠々と行動できるわけではないよね
RLock
こっちは他の人にいきなり権利をぶん取られることはない。
再帰ロックという名の通り、所有中は何回もロックかけれる。なんのためにするんだろ?おかゆに誰か教えて。
Semaphore
これは wikipedia にもあったけど、「図書室のたとえ」が分かりやすかった
ある図書室に学習用の個室が10部屋あり、それぞれの学習室は一度に1人の学生が使用するとする。争奪戦が始まらないよう、学生はフロントデスクで学習室の使用を申し込むことになっている。学習室を使用し終えたら、学生はフロントに立ち寄ってその学習室が空いたことを知らせなければならない。全ての学習室が埋まっている場合、学生はフロントで部屋が空くのを待つ。
フロントの図書係はどの学習室が空いているかは把握しておらず、単に空いている部屋数のみを知っている。学生が申し込んできたとき、図書係はその数から1を引く。学生が学習室の利用を終えたとき、図書係はその数に1を加える。学習室の使用許可が与えられたとき、その部屋は必要なだけ使い続けることができ、したがって事前に学習室を予約することはできない。
Condition
「条件変数」っていうらしい。
自分にとってイイ感じになるまでロック手放して、イイ感じになったらまたロック取得して作業するときに使う
一方で、「共有リソース更新したぞ」ってのを他のスレッドにnotify
できる。この機能のおかげで「いい感じになったら動く」というのができる。
ロックとちがって自発的に待つことができる wait
。待って、誰かが notify してくれたらいそいそと動く、みたいなことができる。
Barrier
協力プレイ系ゲームの「複数人で押さないと動かない岩とか扉」みたいなやつ
.wait
して、指定人数が wait したらブロックが外れる。まさにダンジョンの仕掛け
Event
「よ~い、ドン」
誰かが「いいよ」っていうまで待機するためのもの。
ちょっとConditionと似てるよね。
それっぽい図を書くことで分かった気になるぜ(__init_subclass__)
Python3.6出ましたね!
主要なものとしては
あたりでしょうか。上2つは割とそのままで、3つ目はそもそもasync関連を勉強してからじゃないとなので、4つ目が現状の知識で一番楽しいかなという感じ。
PEP 487 -- Simpler customisation of class creation | Python.org
わかった気になれる図
あまり真に受けないでください
というわけで(ひとつのユースケース)
UMLのクラス図の矢印の方向でも察せる通り、継承関係を知ってるのはサブクラス側だけで、スーパークラスは誰が自分を継承してるのかということは知りません。
なので、下々のクラスどもを一括して管理したいようなとき、1つの方法としては、特定のメタクラスで生成してあげて、そのメタクラス側で生成したクラスのことを覚えておく…というのがあります。メタクラスは自分が誰を生成したのかということは知ってますから、別にどうってことないですよね。(というのが分かった気になれる図の左側)
でもそういうことするためにわざわざメタクラス学ばないといけないというのも学習障壁がしんどい。
というわけで、Python3.6では、めちゃくちゃ大雑把に言って、スーパークラスが「誰が自分を継承したか」ということを知れるようになりました。これによって、誰かが自分を継承したときに何か反応を起こすことができるようになります(__init_subclass__
)し、まさに下々のクラスどもを一括して管理できるようにもなります。
というのは半分嘘で(ええ・・・)、もう少し真面目に言うと、__init_subclass__
はクラスメソッドであり、このメソッドを実装しているスーパークラスを継承する際に、サブクラスがこのメソッドを呼び出します。呼び出すと言っても、そのクラスメソッドは(オーバーライドしない限り)スーパークラスのものですから、「継承時にスーパークラスの__init_subclass__
メソッドを呼び出す」ことには変わりありません。
スーパークラスがサブクラスを弄るという説明は(見た目的にはそれに近いことが起こりますが)微妙で、まあ単に親クラスのクラスメソッドの呼び出しです。
まあ、自分が持ってないクラス変数なら、親を探索するのはどんなメソッドでも同じですから、キモは「継承時呼び出し」というところですね。
PEP-487 の例を参考に(『Effective Python』項目34はまさにこれのメタクラスバージョンです):
class PluginBase: subclasses = [] def __init_subclass__(cls, xxx, **kwargs): super().__init_subclass__(**kwargs) print(cls) cls.subclasses.append(cls) cls.xxx = xxx class C(PluginBase, xxx=10): ... class D(PluginBase, xxx=20): ...
クラスCとDは__init_subclass__
を実装しているPluginBaseのサブクラスであり、これらがPluginBaseを継承するタイミングで、このメソッドが(サブクラスから)呼ばれます(なので、cls
に入るのはサブクラスであるCやDです)。
PluginBase は subclasses リストをクラス変数として持っており、そこに呼び出したクラス cls
を追加していくわけですね(cls.subclasses.append(cls)
に10分くらい戸惑っていましたが、cls.subclasses
でスーパークラス(PluginBase)の変数を参照しようとしてるわけですね。でもこれ、C, Dがクラス定義でsubclasses
変数持ってたらそっちにアクセスされちゃうのでダメです。なので、__class__.subclasses
でアクセスするのがより安全だとは思います)。
で、今まで親クラスやメタクラスなど書いてたところに、__init_subclass__
が引数として取る値を入れれるようになりました。上の例ではxxx
がそれです。指定外のキーワード引数は kwds に吸収されるっぽい。
これが何に使えるのか…というところですが、メタクラスの代理としてスーパークラスを用いるためとしてこの機能があることを踏まえると、今までのクラス生成時におけるクラス変数の調整のところを代替できるようになるんじゃないですかね(しらんけど)。
もうちょいまともに見てみる
メタクラスがそれに従うクラスを生成するときに引数としてとるのは
- クラス名
- 親クラスのタプル
- そのクラスの属性辞書
の3つです。ところで、__init_subclass__
においては、この3つは
- クラス名:
cls.__name__
- 親クラスのタプル:
cls.__mro__[1:]
- 属性辞書:
cls.__dict__
でアクセス可能ですから、実際、メタクラスの__new__
でできたことの大抵は__init_subclass__
でできるようになったんじゃないかと思います。
『Effective Python』の項目33, 34, 35 のようなことは__init_subclass__
(と__set_name__
)で実現できるようになったと思います。
たとえば、具象クラスのチェックが抽象クラスに書けるようになったのはなかなかお得な感じがします。Effective Python 33のソースコードを書き換えてみました:
class Polygon: sides = None @classmethod def interior_angles(cls): return (cls.sides - 2) * 180 def __init_subclass__(cls, sides, **kwds): if sides < 3: raise ValueError("Polygons need 3+ sides") cls.sides = sides class Triangle(Polygon, sides=3): pass class FalsePolygon(Polygon, sides=2): pass
Polygon を基底クラスとして、これを継承するサブクラスの sides は3以上であることをサブクラス生成/継承時にチェックします。
sides をわざわざ引数として渡さずに、クラス定義の中に書いてしまってもいいとは思いますが、まあチェック時に値を属性辞書から取り出すようにするかどうかの違いしかないですね。
当該PEPによれば、__init_subclass__
はメタクラスの__new__
内部で実行されるようなので、ここで例外があがればクラス自体生成されないことになるようです(この辺りはインスタンス生成の__init__
と挙動が少し異なりますね(インスタンスの場合、__init__
が失敗してもインスタンス自体は作られてしまうので))。
この例を見ると、抽象基底クラス(ABC)とかなり相性がよさそうに見えますね!今までは具象クラスで実装してほしいクラス変数は、ABCではとりあえず None にしておいて書いておくという方法しかなかったですが、__init_subclass__
の引数として必須にしておけば、具象化のためにその値が必要だということが分かる上に、姑息にNoneを入れて変数の存在を示唆する必要もなくなりますので、お役立ちって感じです。
あと(ケースとして必要かは微妙ですが)、サブクラスはその属性をもっていてほしいが、スーパークラスには持たせたくない、かつ、だけどサブクラスでその属性の値を指定しない場合はデフォルト値を想定したい、というようなときは、デフォルト引数としてその値を渡せばいいですよね。
おわり
メタクラスのメタな操作がスーパークラスまで降りてきたことで、メタいことが学習障壁を低くして実現できるようになったってだけの話ではありますが、ABCがその具象化に対する責任を持てるようになったというのは結構偉大なことかとは思います。
とはいえ、単純にメタいことをスーパークラスに書いちゃえるということは、それだけ概念的に混乱するリスクも増えるということでしょうし、その辺りは注意しないといけないかもしれません。
まあメタメタ言ってますが、スーパークラスのサブクラスへの干渉という観点でみれば、そんなメタメタしくはない気もします。見方によりますね。上の方で半分嘘と言いながら説明した「スーパークラスが誰から継承を受けたか知れるように(そしてある程度操作できるように)なった」という観点が増えたと思えばいいかもしれない。学習障壁云々の話をしましたが、実際じゃあこのカスタムメソッドを説明するときに「今まではメタクラスでやってたことがね」という導入はメタクラスの概念を持ちこんでしまってるわけであまりよろしくはないと思います。メタクラスで実現していたという事実はあったとしても、あくまでクラスレベルでの関係性としてこのカスタムメソッドを捉えて説明しないことには本格的な学習障壁の低下にはならんでしょう。そうなると、「スーパーからサブ方向への接触を可能にする」という形になるんじゃないかと思います。
話かわって。THEメタといえば、動的なクラス生成がありますが、これもそんな凝ったものでなければtypeと__init_subclass__
だけで十分まかなえそうだし、クラスレベルでかなりやれることが増えたなという感じはします!
ま、メタクラスを使わない方法でいえば、今までもクラスデコレータがありましたけどね!それに加えてもう一つ新しい方法が増えたというくらいに考えるのがいいかも。
もやもやのメモ
構造体のセレクタ
セレクタは自動的に構造体へのポインタの間接参照を行います。xが構造体へのポインタ型のときx.yは(*x).yの簡略形として使用可能です。フィールドyも同じく構造体へのポインタ型のときx.y.zも同様に(*(*x).y).zの簡略形です。xが*A型の匿名フィールドを持ち、かつAも同様に構造体であるなら、x.fは(*x.A).fの簡略形です。
どおりで!
new()
組み込み関数newはパラメータに型Tを取り、型*Tの値を返します。
なるほどなあっ!
make()
スライス、マップ、チャネルはnewによる間接的なメモリ割り当てを必要としない、参照型です。組み込み関数makeはパラメータに型Tを取ります。このTはスライス、マップ、チャネル型でなければなりません。またオプションとして式のリスト(作成する種類によって異なる)を取ります。makeはT型(*Tではない)を返します。
なるほどなあっっ?
スライスが配列へのポインタをもって云々というのは分かる(参照してる感が理解できる)けど、マップはどうしてるんだろ?キャパシティ云々のこと考えると、やっぱバックで適当に配列見繕ってマップパターンを実現してるのかな?
日本人のロジバン学習ロードマップ(たぶん)
オチ
ここを適宜読もう!
はじめに
この記事は、日本人(注:英語の教材を読むよりは日本語の教材を読む方が楽な人のことを指しています)がロジバンを学びたいときにどこを読んでいけばいいのかというのを書いたものです。
ですが、やっぱり最終的には英語のドキュメントにあたることになります。仕方ないですね。英語力も鍛えてください。
ここでは、できる限り基本は日本語で学べるような道を用意します。
まずはここから
間違ったスタートダッシュ
やめときましょう(先人のアドバイス)。
正しいスタートダッシュ
は?宣伝です。
とは言え、ロジバンに興味をもって学ぼうとした人が訪れる場所は大抵「はじロジ」っぽいので、やっぱりここをオススメします。
「ニューゲーム」はほぼ完成(メンテナンスのみ)しているので安心して読めますが、その続編である「つよくてコンティニュー」はいまだ執筆中のようです(ようです?)。
とはいえ、とりあえず「ニューゲーム」だけでも読んでおけば、語彙も基本中の基本はそこそこ、文法は割りかし適度に追えるようになれるはずです。「つよコン」は…、出来上がってるところまで読んで、書き上がり次第フォローしていきましょう。
サプリメント
「はじロジ第2版」は音声に関してはめっきりなので、発音が気になるという人はこちらもオススメします:
日本語の音声講座で、毎回の終わりに発音講座があります。そこだけ聞いてもよし、もちろん文法講座のところも聞いてよし。
その次は?
「はじロジ2」が完結すれば、この寄り道はいらないんですが、現状はここに立ち寄るのがベターらしいです。
「はじロジ2」を読んでいれば、入門コースは飛ばしても大丈夫だと思います。「PSの傾向」くらいは読んでてもいいかもしれない(雑学程度だけど)。
初級コースをかいつまみましょう。たとえば:
- 品ある品詞:brivla の下位分類、gismu, lujvo, fu'ivla に関する簡単な説明がある
- 命題を項に(抽象詞):{tu'a}の説明がある
- 項で項を(pe,ne):{po}, {po'e} の説明がある(けど、実際に使う機会はほとんどない気がするので飛ばしてもOK)
- アレ、云々(省略語):そこまで重要な話でもないが、体系的に語を覚えたいなら。
冠詞のところ、数詞のところ、分配性と集団性は読まなくてもいいです。というか読まないほうがいいかもしれない。理由は単に「分かりにくい」からで、はじロジ2でもっと分かりやすい解説がくるのを待ったほうがいいです。
補助資料や補習資料も気になるものに目を通せばいいと思います。
サプリメント
分配性と集団性についてどうしても気になる人で、かつ、論理学の素養がある程度ある人はここを読むのをおすすめします:
gadri の論理学的観点からの解説 - La Lojban
ただ…、まあそんな気にするものでもないです(アドバイス)。最近のロジバンはこのあたり特に考えなくても簡単に使えるようになっています。
その次は?
lojban wave lessons(おかゆはLWLと略す)を読みましょう!日本語訳版は現在2つあって、現在はlojban wikiの方がメインです。経緯としては、まず「味噌煮込みロジバン」での日本語訳があり、それが wiki に移行されました:
lojban wavelessons 日本語版 - La Lojban
はじロジと難易度・内容はそこまで変わりませんが、定番の講座ではあるので、一読をおすすめします。はじロジとは異なる視点でロジバンを理解しておくのも大事なことです。
サプリメント
Lojban Wave Lessons - La Lojban
英語版(原書)です。最後の数章は日本語に訳されていないので、どうしても読みたい場合は英語で読むことになります(が、まあこの時点で読まなくてもいい気はしないでもないです)。
割と読んできたけど?
ロジバン大全: The Complete Lojban Language 日本語抄訳
おかゆはCLLと略します。いわゆるロジバンの仕様書のようなものです。が、微妙に仕様が古いところがあります(特に冠詞まわりの意味論)。ので、あくまで参考ということで読むのがいいと思います。細かい仕様はCLLを見ないとどうにもならないことは確かにあります。
ただ、完読は結構しんどいとは思うので、気になるところだけ読みましょう。20章の「セルマホ目録」は便利な索引です。
サプリメント
LRG。CLL日本語抄訳の底本(1.0)の改訂版。
さらにロジバンを知りたい人へ
ここからは英語になります。
BPFKは、ロジバンの意味論をいまいちど整理しようとして動いている委員会(公式)です。BPFK Sections は委員会によって整理・提案されている各機能語の意味論が載っています。CLLが微妙に気に食わないときはここを読んで気に入りましょう。
BPFK sections に載ってない場合は、各メーリスを覗くと色々分かるかもしれません。
あ、あと、wiki 見ると何かあるかもしれませんね:
このあたりを調べ尽くして無いようなら、多分ないです。
おわりに
まあオチは最初に書いたので言うことはないんですが、とりあえずこっちに来て一緒に苦しみ楽しみましょう!
ありふれたクソみたいな記事(シーケンスの速度比較)
括弧が逆ゥ!
はてブ見てたらこういうの見つけた:
Python簡単実験:内包で何倍高速になるか - TIM Labs
「なんで対話型インタプリタ使ってるんや・・・・・?」という疑問は置いといて、こんなに違うのかとちょっと信じ切れなかったので、自分でも書きました。
まずは4つ
作ったのは4つで、
- 単純に for で回す
- 内包表記使う
- range をそのまま list に投げる
- ジェネレータを list に投げる
です。
# coding=utf-8 import time def time_calc(func, *arg, **kwds): start = time.time() func(*arg, **kwds) end = time.time() return end - start def calc(func, n, *arg, **kwds): times = [time_calc(func, *arg, **kwds) for _ in range(n)] return sum(times) / n def simply_for(n): a = list() for i in range(n): a.append(i) return a def comprehension(n): a = [x for x in range(n)] return a def range_case(n): a = list(range(n)) return a def range_(n): for i in range(n): yield i def generator_case(n): a = list(range_(n)) return a if __name__ == '__main__': n = 10**7 for func in (simply_for, comprehension, range_case, generator_case): print("{}: {}sec.".format(func.__name__, calc(func, 5, n)))
おかゆのパソコンはうんちなのですが、とりあえずこうなりました*1:
simply_for: 2.1997323036193848sec. comprehension: 1.3124329090118407sec. range_case: 0.550351619720459sec. generator_case: 2.0610249996185304sec.
内包表記、うちのぱちょこんだと2倍も速くならないくらいですね。まあ速いことに変わりはありません。
それにしても、list(range(n))
が思った以上に速いですね…。
上の記事だと、単純な for より内包表記は約5倍ほど高速になってることを考えると、list(range) は10倍くらい速いことになりそう。
とはいえ、値を計算して…というのはできないですから、結局、内包表記使うのが速くていいですね。
あとは…、おかゆパソコンだと非常に微妙ですが、130msほどジェネレータつくってやるほうが速いですね。
内包表記はforが入れ子になると表記が煩わしくなるので、そういうときはジェネレータ作ってやると for よりは速いということになるのかもしれない(オーバーヘッドはあるだろうけど)。
からの入れ子3つ
というわけで、入れ子3つにしてみた:
def simply_for_2(n): a = list() for i in range(n): for j in range(n): for k in range(n): if i+j+k % 2 == 0: a.append(i+j+k) return a def comprehension_2(n): a = [i+j+k for i in range(n) for j in range(n) for k in range(n) if i+j+k % 2 == 0] return a def generator_2(n): for i in range(n): for j in range(n): for k in range(n): if i+j+k % 2 == 0: yield i+j+k def generator_case_2(n): a = list(generator_2(n)) return a if __name__ == '__main__': n = 300 for func in (simply_for_2, comprehension_2, generator_case_2): print("{}: {:.3f}sec.".format(func.__name__, calc(func, 3, n)))
simply_for_2: 8.179sec. comprehension_2: 7.936sec. generator_case_2: 7.853sec.
び、びみょ~~~~~~~~~~~~~~~( ᕦ 。 ᕤ)
入れ子が増えると内包表記もfor並にとろくなるのかしら。そう考えると、コードの見た目の観点から普通にfor書くべきだね。
ジェネレータは安定して、「微妙に」速い。
併用や!
見た目的にも、かつ、汎用性も兼ねることを考えると、ジェネレータと内包表記を併用するのが良さそう?
def generator_3(n): for i in range(n): for j in range(n): for k in range(n): yield i, j, k def generator_and_comprehension(n): a = [i+j+k for i, j, k in generator_3(n) if i+j+k % 2 == 0] return a if __name__ == '__main__': n = 250 for func in (simply_for_2, comprehension_2, generator_case_2, generator_and_comprehension): print("{}: {:.3f}sec.".format(func.__name__, calc(func, 3, n)))
simply_for_2: 4.445sec. comprehension_2: 4.476sec. generator_case_2: 4.380sec. generator_and_comprehension: 7.460sec.
ひえっ( ᕦ 。 ᕤ)お、おしょい・・・!
まあ恐らく原因は、forが実質1つ増えてるというところでしょうかな……。
ジェネレータで入れ子forを分離してやるというのは、個人的には見た目すごいスッキリして好きなんだけどな…。
ちなみに、
def generator_and_for(n): a = [] for i, j, k in generator_3(n): if i+j+k % 2 == 0: a.append(i+j+k) return a if __name__ == '__main__': n = 250 for func in (generator_and_comprehension, generator_and_for): print("{}: {:.3f}sec.".format(func.__name__, calc(func, 3, n)))
generator_and_comprehension: 7.505sec. generator_and_for: 7.465sec.
なので、もうジェネレータ律速って感じがしますね。内包表記の速みはどこにいったのか……?
itertoolsや!
from itertools import product def product_and_for(n): a = [] for i, j, k in product(range(n), range(n), range(n)): if i+j+k % 2 == 0: a.append(i+j+k) return a def product_and_comprehension(n): a = [i+j+k for i, j, k in product(range(n), range(n), range(n)) if i+j+k % 2 == 0] if __name__ == '__main__': n = 250 for func in (generator_and_comprehension, generator_and_for, product_and_for, product_and_comprehension): print("{}: {:.3f}sec.".format(func.__name__, calc(func, 3, n)))
いけー!ぷいきゅあがんばぇー!
generator_and_comprehension: 7.478sec. generator_and_for: 7.412sec. product_and_for: 4.933sec. product_and_comprehension: 4.959sec.
!!
普通に回すよりは若干遅くなるものの、product の力が伺えますね。これが標準ライブラリ(というかC)のちからか…!
というわけで
単一 for なら内包表記で、入れ子なら itertools.product 使って単一 for で回すのがキレイにそこそこ速そう。
10.1. itertools — 効率的なループ実行のためのイテレータ生成関数 — Python 3.5.2 ドキュメント
ちなみに、product(range(n), range(n), range(n))
は product(range(n), repeat=3)
とも書けます。
ちなみに
単一 for でも、回したいシーケンスが range では上手く表現できないときは、内包表記渡すかジェネレータ式渡すか、ですが、メモリ的なこと考えるとジェネレータ式渡したほうがよさそうではある?
面倒なのでやりませんけどね!😇
てか
なんでこんなに内包表記が遅くなったんでしょ?
def if_for(n): a = list() for i in range(n): if i % 2 == 0: a.append(i) return a def if_comprehension(n): a = [i for i in range(n) if i % 2 == 0] return a if __name__ == '__main__': n = 10**7 # for func in (generator_and_comprehension, generator_and_for, # product_and_for, product_and_comprehension): for func in (if_for, if_comprehension): print("{}: {:.3f}sec.".format(func.__name__, calc(func, 3, n)))
if_for: 2.967sec. if_comprehension: 2.430sec.
ふむふむ。内包表記 if
いれるだけでこんなに時間が接近するとは。
もしかして、入れ子がダメなのか?
def for_for(n): a = list() for i in range(n): for j in range(n): a.append(i+j) return a def comp_comprehension(n): a = [i+j for i in range(n) for j in range(n)] return a if __name__ == '__main__': n = 3500 # for func in (generator_and_comprehension, generator_and_for, # product_and_for, product_and_comprehension): for func in (for_for, comp_comprehension): print("{}: {:.3f}sec.".format(func.__name__, calc(func, 3, n)))
for_for: 3.454sec. comp_comprehension: 2.177sec.
ほほう🤔約1.5倍なので、これは最初にやった単一forと変わらないですね。
内包表記、if 入れると遅くなるのか……?
結論
if はヤバイ(?)
*1:桁を切れ