PyData.tokyo One-day Conference 2018 で登壇させていただきました
発表資料
PyData.tokyo One-day Conference 2018 で登壇させていただきました
発表資料
これまで、python.jp ではSlackチームを用意していたが、こちらの利用は取り止めて、Discord に移行することにした。
書き込みはそれほどなかったものの、Python.jpチームには、約1000アカウントが登録されていた。そこそこな規模だろう。Slack->Discordへの大規模な移行としては Reactチーム の例があるが、こちらは別に Slack からなにかの制限を受けたというわけではない。
では、なぜDiscordに移行するかといえば、Slackというのはやはりオープンなコミュニティのチャットツールとしてはイマイチだと思うからだ。ReactさんのBlog にあるように、Slackのユーザ登録は面倒だし、複数のチームに所属する場合はそれぞれのチームでいちいちログインしなければならない。コミュニティ用のツールではないので特定ユーザのミュートやBanなどの機能もない。
また、Slackで一番問題だと思うのは、無料のフリープランでは過去の書き込みを最大10000件までしか参照できない点だ。もちろん、払うものを払えばいいのだが、1000ユーザだと月85万円になる。道楽で払う額ではない。
この点、Discordの場合はログの保持件数に制限がない。無料で使うのは気が引ける、という人は、個別に 月額$4.99 でDiscordをサポートできる。チームごとに運営者がまとめて支払う必要はない。
機能的にSlackではできてもDiscordではできなくなる点がいくつかあるが、多少の機能不足はあっても、ログ保持件数の制限がないというメリットのほうが重要だと思う。でも、Discordさんなんとか対応してくれないかなと願う次第だ。
Slack では、それぞれの書き込みへのリンクを取得してあとから参照できるが、Discord にはこの機能がない。ちょっと離れた書き込みへのリプライなどは書きにくそうだ。
Discord には、SlackBot に相当する簡易Bot機能がない。また、GoogleやTwitterなどと連携するbotもデフォルトでは用意されていないので、各サーバが自前で構築する必要がある。
Discord では最大50件しか登録できない。これだと、全ユーザに権限を開放して自由に登録できるようにするのは、ちょっと躊躇してしまう。
SlackのReply見にくくてきらいだから別にいい
Python.jp Slackチームは、2016年12月30日 に公開された。それから約1年半、とくに宣伝はしなかったが、コンスタンスに利用者が増え続け、いつの間にか1000名を超える大所帯となっていた。
もともと何らかのプランがあって作成したわけではなく、とりあえず建ててみるかー程度のノリだった。特に目的や利用方法を決めていたわけではないが、これだけの人々が登録しに来るというのは、やはりそれなりのニーズがあるのだろう。
しかし、Python.jp Slackチーム、登録者数は多くても、たまに初心者の質問があったりする程度で、書き込みはほとんどなかった。どんなコミュニティでもROMが主流なのは同じだろうが、さすがに書き込みがなさすぎる。1000名のPythonistaが息をひそめてSlackを見つめているかと思うと、なんだかこっちまで息が詰まる。
というわけで、なにかPythonに関することで、ちょっと話したいことや、人に尋ねたいことがあったら、気軽に python.jp Discordサーバ に書き込んでいただきたい。
Chatというのはあまり細かいことを気にせずに書き込める、というのが身上だと思う。気軽に雑談するつもりで利用していただけるようなサーバとして運営したいと思っているので、ご協力いただければ幸甚だ。
Dell XPS 13 (9370) の全部入り( i7-8550U・4Kタッチパネル・1TB SSD ・16 GBメモリ) を購入した。
これまでのところ、思ったより使い勝手がよい。キーボードはいい感じだし、パームレストのカーボンファイバも触り心地が良い。
液晶は非光沢がなくて光沢のみということでターミナルの背景におじさんが出現する不具合が怖かったが、あまり気にならない。手元のMacBook Airと比べても遜色ない気がする。
Linux環境でのタッチパッドも、設定でかなりまともに使えるようになった。 ← 後述のとおり、16.04ではダメだったので17.10に切り替えた
以下作業メモ
どっちにしろWindows 10 Proをインストールするので、プリインストールのWindows 10 Homeは潔くパーティションごと削除。
UbuntuとWindowsのインストール用USBを作成する。 XPS 13 (9370) では、Type-AのUSBメモリがいくつあっても無意味。Type-C - Type-A の変換アダプタを用意する。
Ubuntu 16.04もWindows 10も、インストールメディアで立ち上げた状態ではWiFiとBluetoothが使えないので、USB Type-C で使えるテザリングかLanアダプタを用意する。
XPS 13を再起動し、Dellのロゴが表示される前にF2キーを連打し、BIOS設定画面を表示する。
BIOS画面で、
UbuntuのUSBメモリを挿し、再起動する。Dellのロゴが表示される前にF12キーを押し、ブートデバイスにUSBを選択する。
Ubuntu 16.04インストールメディアに含まれているドライバでは、XPS 13のBluethoothやWiFi、画面のスケール設定などが動作しない。
とりあえず 16.04 をインストールし、LanアダプタやスマホのUSBテザリングなどを使って sudo apt update; sudo apt upgrade
して再起動すれば正常に動作する。
[追記] 以下の手順で16.04にlibinputをインストールすると、ログインーログアウトを繰り返すとハングする模様。。。
デフォルトの状態ではタッチパッドのpalm detectionまじ使えないので、次のページを参考に libinput をインストールする。
$ sudo apt install xserver-xorg-input-libinput
# Match on all types of devices but tablet devices and joysticks Section "InputClass" Identifier "libinput pointer catchall" MatchIsPointer "on" MatchDevicePath "/dev/input/event*" Driver "libinput" EndSection Section "InputClass" Identifier "libinput keyboard catchall" MatchIsKeyboard "on" MatchDevicePath "/dev/input/event*" Driver "libinput" EndSection Section "InputClass" Identifier "libinput touchpad catchall" MatchIsTouchpad "on" MatchDevicePath "/dev/input/event*" Driver "libinput" Option "Tapping" "True" Option "NaturalScrolling" "True" EndSection Section "InputClass" Identifier "libinput touchscreen catchall" MatchIsTouchscreen "on" MatchDevicePath "/dev/input/event*" Driver "libinput" EndSection Section "InputClass" Identifier "libinput tablet catchall" MatchIsTablet "on" MatchDevicePath "/dev/input/event*" Driver "libinput" EndSection
この状態だとタッチパッドの設定をGUIで行えないので、次のページを参考に設定を行う。
touchpad_id=$(xinput --list | grep -i "Synaptics Touchpad" | xargs -n 1 | grep "id=" | sed 's/id=//g') # touchpad_id='13' natural_scrolling_code=$(xinput --list-props "$touchpad_id" | grep "Natural Scrolling" | awk '{print $5}' | grep -o '[0-9]\+') xinput --set-prop "$touchpad_id" "$natural_scrolling_code" 0 tap_to_click_code=$(xinput --list-props "$touchpad_id" | awk '/Tapping Enabled \(/ {print $4}' | grep -o '[0-9]\+') xinput --set-prop "$touchpad_id" "$tap_to_click_code" 1 accel_speed_code=$(xinput --list-props "$touchpad_id" | awk '/Accel Speed \(/ {print $4}' | grep -o '[0-9]\+') xinput --set-prop "$touchpad_id" "$accel_speed_code" "1.0" # -1.0 ~ 1.0 disable_while_typing_code=$(xinput --list-props "$touchpad_id" | grep "Disable While Typing Enabled (" | awk '{print $6}' | grep -o '[0-9]\+') xinput --set-prop "$touchpad_id" "$disable_while_typing_code" 1
Windows 10 Fall Creators Updateのインストールメディアでは、WiFi/Bluetooth が認識できない。
http://www.dell.com/support/home/jp/ja/jpbsd1/products/laptop/xps_laptop?app=drivers を参照し、Dell Mobile Connect Driver
をインストールするとWifiに接続できる。
リブートすると、デフォルトでWindowsがブートしてしまうので、Ubuntuのgrubでブートする場合は、Ubuntuで Boot-repair
をインストールして設定を変更する。
雑に図ってみた (Python 3.6.4)
対象ファイル: 画像ファイル約20000件(2GB弱)
アルゴリズム | 処理時間(秒) ファイル読み込み時間は含まず |
---|---|
md5 | 2.536 |
sha1 | 1.798 |
sha256 | 3.873 |
sha384 | 2.591 |
sha512 | 2.626 |
sha1はええ。md5そんな速くねえ。sha512とそんな変わんねえ。
import hashlib, sys, pathlib, time sizes = [] times = [] HASH = gatattr(hashlib, sys.argv[1]) def h(b): f = time.time() h = HASH(b).digest() times.append(time.time()-f) sizes.append(len(b)) imgs = pathlib.Path(sys.argv[2]).glob("*.jpg") for i in imgs: h(i.read_bytes()) print(len(sizes), sum(sizes), sum(times), sum(times)/len(times))
ふと思い立って、CUDAでCPUからGPUにデータを転送する時の速度を測ってみた。
普通にCUDA SDKのサンプルで測定しても良いが、PythonのCFFI で実行してみよう。
コードはこんな感じで書ける。
import time import sys from cffi import FFI ffi=FFI() ffi.cdef(r""" typedef unsigned int cudaError_t; cudaError_t cudaGetLastError(); char* cudaGetErrorString(cudaError_t error); cudaError_t cudaDeviceSynchronize(); cudaError_t cudaMalloc(void **p, size_t s); cudaError_t cudaFree(void *p); cudaError_t cudaMallocHost(void **p, size_t s); cudaError_t cudaFreeHost(void *p); cudaError_t cudaMemcpy(void *dst, const void *src, size_t count, unsigned int kind); """) cudaMemcpyHostToHost = 0 cudaMemcpyHostToDevice = 1 cudaMemcpyDeviceToHost = 2 cudaMemcpyDeviceToDevice = 3 cudaMemcpyDefault = 4 cuda = ffi.dlopen("/usr/local/cuda/lib64/libcudart.so") def check(): err = cuda.cudaGetLastError() if err: print(ffi.string(cuda.cudaGetErrorString(ret))) assert err == 0 def run(host, dev, n, size): for i in range(n): ret = cuda.cudaMemcpy(dev[0], host[0], size, cudaMemcpyHostToDevice) check() size = int(sys.argv[1]) pinned = ffi.new("void **") cuda.cudaMallocHost(pinned, size) check() ffi.memmove(pinned[0], b'a'*size, size) dev = ffi.new("void **") cuda.cudaMalloc(dev, size) check() run(pinned, dev, 1, 1) # warm up buf = ffi.new("char []", b'a'*size) host = ffi.new("char **", buf) N = 1000 for s in range(1, size, size//20): f = time.time() run(pinned, dev, N, s) p = time.time()-f f = time.time() run(host, dev, N, s) n= time.time()-f print(f'{s}, {p:0.5f}, {n:0.5f}')
AWSのp2.xlargeとp3.2xlargeで実行してみた。
p2.xlarge Tesla K80 E5-2686 v4 @ 2.30GHz
p3.2xlarge Tesla V100-SXM2-16GB E5-2686 v4 @ 2.30GHz
ざっくり、Pinnedメモリのほうが15%くらい速い。また、転送速度に関してはp2.xlargeでもp3.2xlargeでも大差はないようだ。
ちなみに、手元にあったi7-6700 CPU @ 3.40GHz/GeForce GTX 1070 と E5-1650 v4 @ 3.60GHz/GeForce GTX 1080 Ti のデスクトップは、EC2のインスタンスより2割程度高速だった。
また、~100KBぐらいまでの転送量を見てみると、50KBぐらいまではPinnedメモリより普通のメモリのほうが速いようだ。
これは、データの転送よりも、転送の前処理・後処理に時間がかかってしまっているのかもしれない。ストリームを使って並列化するなどすればまた違ってくるかもしれないので、あとで実験してみたい。
さて、 リスト内包のひみつ - atsuoishimoto's diary で、Python3では、リスト内包式は関数呼び出しとなることを説明した。
>>> a = [i*2 for i in range(3)]
というスクリプトは、次のように展開される。
>>> def _listcomp(_it): ... ret = [] ... for i in it: ... ret.append(i*2) ... return ret ... >>> _it = range(3) >>> a = _listcomp(it)
通常、この点はあまり気にする必要はないが、問題となるケースもなくはない。
クラスブロックで次の処理を実行してみよう。
class Foo: NUMS = [i*2 for i in range(3)]
まあ、これは当然動作する。Foo.NUMS
の値は、[0,2,4]
となる。
では、これをちょっと直してみよう。
class Foo: N = 3 NUMS = [i*2 for i in range(N)]
これも問題ない。Foo.NUMS
の値は、同じく [0,2,4]
となる。
もうちょっと変えてみよう。
class Foo: N = 3 F = 2 NUMS = [i*F for i in range(N)]
一見、これも問題なさそうだが、実行するとエラーとなってしまう。
>>> class Foo: ... N = 3 ... F = 2 ... NUMS = [i*F for i in range(N)] ... Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 4, in Foo File "<stdin>", line 4, in <listcomp> NameError: name 'F' is not defined
なんで N
は良くて、 F
はダメなのだろう。
Pythonが spam*2
のような式の値を計算する時、Pythonは変数 spam
の値を、次の順序で検索する。
例えば、
def foo(): return 2 * spam
という関数を実行すると、Pythonは変数 spam
を
foo()
のローカル変数。foo()
が所属するモジュールのグローバル変数。__builtins__
。の順番で検索し、見つからなければ NameError
例外が発生する。
この名前の解決ルールは、Pythonプログラミングにおいて非常に重要なルールだ。それほど難しいルールではないので、確実に頭に叩き込んでおこう。
Pythonのプログラムを読んでいて、どこで定義されているかわからない変数や関数などがあっても、「ローカル変数かグローバル変数か __builtins__
のいずれかに必ず存在する」 ということも覚えておくと良いだろう。
Pythonの名前解決ルールを頭に叩き込んだら、次のコードを見てみよう。
class Foo: N = 3 M = 2*N
当然ながら、Foo.M
の値は 2*3=6
となる。2*N
という式は N
という変数を参照しているが、Pythonは同じブロックで定義されている変数 N
を見つけ、値を取得する。
では、このコードをちょっと修正しよう。
class Foo: N = 3 def f(): return 2*N M = f()
このコードを実行すると、次のようなエラーが発生する。
>>> class Foo: ... N = 3 ... def f(): ... return 2*N ... M = f() ... Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 5, in Foo File "<stdin>", line 4, in f NameError: name 'N' is not defined
関数 f()
内で、変数 N
が見つからないと言っている。
しかし、
N = 3 def f(): return 2*N M = f()
この処理は、どう見ても問題のないコードだ。実際、クラス定義以外で実行すると、正常に動作する。
次の例では、関数 f()
の式 2*N
は、グローバル変数 N
を参照して値を評価する。
>>> N = 3 >>> def f(): ... return 2*N ... >>> M = f() >>> M 6
また、次の例では、入れ子の関数 f()
は、親関数 bar()
のローカル変数 N
を参照する。
>>> def bar(): ... N = 3 ... def f(): ... return N*2 ... print(f()) ... >>> bar() 6
では、さきほどの例をもう一度見てみよう。
>>> class Foo: ... N = 3 ... def f(): ... return 2*N ... M = f() ...
Foo.f()
は変数 N
を参照しているが、この N
はどこで見つかるだろうか?
関数 Foo.f()
のローカル変数ではない。また、Foo.f()
は入れ子の関数ではないので、親となる関数も存在しない。
Foo.f()
のモジュールにも、N
は存在しない。
__builtins__
モジュールにも、当然 N
は存在しない。
「でも、Foo.N
があるじゃないか!」と言うかもしれない。しかし、
Foo.N
は、Foo.f()
のローカル変数ではなくFoo.N
は、Foo.f()
のモジュールのグローバル変数ではなくFoo.N
は、__builtins__
モジュールの値でもないつまり、Pythonの名前解決ルールのどの項目でも、Foo.f()
では、 N
という名前を解決することはできないのである。
さて、冒頭のコードをもう一度見てみよう。
class Foo: N = 3 F = 2 NUMS = [i*F for i in range(N)]
このコードは、次のように実行される。
class Foo: N = 3 F = 2 def _f(_iter): ret = [] for i in _iter: ret.append(i*F) _iter = range(N) NUMS = _f(_iter)
リスト内包式が関数に変換されるのは、リスト内包式の []
内のすべてではなく、in
以下のイテレータを指定する式は関数内では実行されない。
したがって、
class Foo: N = 3 F = 2 NUMS = [i*F for i in range(N)]
の、 range(N)
の部分は、そのままクラスブロックで実行されるため NameError
とはならない。
しかし、i*F
の部分は関数内で実行されるため、クラス変数 F
を参照できず、NameError
とはなってしまうのである。
これはリスト内包だけではなく、ジェネレータ式や辞書内包、集合内包でも同様にエラーとなる。
実のところ、クラスブロックに直接リスト内包を記述する場合は、うまい回避方法は思いつかない。
リスト内包を避けて通常のループを使用するか、または
class Foo: N = 3 F = 2 def _init(N, F): return [i*F for i in range(N)] NUMS = _init(N, F)
のように、関数でラップするのが無難だろう。
こちらのTweetが Python.jp slack でちょっと話題になっていた。
どういうこと? pic.twitter.com/BxyyWbyvQo
— ahuglajbclajep (@ahuglajbclajep) 2018年1月24日
次のようなコードだ
>>> a = [lambda: print(i) for i in range(3)] >>> for i in a: i() 2 2 2
結論としては cocoatomo さんの書かれているように、変数の評価タイミングの問題で、
初めまして.
— tomo🐧 (@cocoatomo) 2018年1月24日
そこは Python のループでよくハマるポイントで, i の値の評価が後で行われるのが混乱の原因です. ループの本体の中で一度 i を別の変数に入れるなどして, 評価を走らせると回避できます.
FAQ → https://t.co/5iCqdIIhUZ
対処としては、次のように、出力する値を、関数の実行時ではなく、関数の作成時に決定する必要がある。
>>> a = [lambda x=i: print(x) for i in range(3)] >>> for i in a: i() 0 1 2
もともとの問題はこれで解決するのだが、このコード、単純だがPython2とPython3では動作が異なっている。
Pythonの動作を理解するのに良い教材だと思うので、ちょっと解説してみよう。
Python2では、先程のコードは、次のようなループとして実行される。
>>> from __future__ import print_function >>> a=[] >>> for i in range(3): ... f = lambda : print(i) ... a.append(f) ... >>> for i in a: i() 2 2 2
リスト内包式で使われているループ変数 i
は、グローバル変数 i
として、リスト内包式を終了した後も参照できる。
i
の値は、必ず 2
となる。
>>> a = [lambda: print(i) for i in range(3)] >>> i 2
この場合、lambda
関数の print(i)
という式は、グローバル変数の i
を参照して、出力している。
したがって、 lambda
関数を実行する前に i
の値を変更すると、出力も変化する。
>>> a = [lambda: print(i) for i in range(3)] >>> i = 10000000 >>> for u in a:u() ... 10000000 10000000 10000000
Python3では、リスト内包は次のように展開され、関数呼び出しとして実行される。
>>> _it = range(3) >>> def _listcomp(_it): ... ret = [] ... for i in it: ... ret.append(lambda : print(i)) ... return ret ... >>> a = _listcomp(_it) >>> for i in a: i() 2 2 2
Python2と違って、Python3ではループ変数 i
は外部からは参照できない。リスト内包は関数内で実行され、 i
はグローバル変数ではなくローカル変数となるためだ。
>>> a = [lambda: print(i) for i in range(3)] >>> i Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name 'i' is not defined
Python2 では lambda
関数の print(i)
は、グローバル変数 i
を参照するが、Python3 では、lambda
式はローカル変数 i
を参照するクロージャとなる。
クロージャとは、Pythonの関数のなかに別の関数を作成した時、親となる関数のローカル変数を子となる関数が参照する仕組みのことだ。
クロージャの仕組みは、以前
に書いた。
したがって、Python2のように、後から i
の値を変更することはできない。
>>> a = [lambda: print(i) for i in range(3)] >>> i = 10000000 >>> for u in a:u() ... 2 2 2
Python2 と Python3 では、リスト内包の実現方法に上記のような違いがある。これは、Python2 では
>>> i = 10000 >>> a = [lambda: print(i) for i in range(3)] >>> print(i) 3
のように、リスト内包式があると他のローカル変数を上書きしてしまい、わかりにくいという問題を解決するためだ。
この経緯は
でも解説されているので、参照されたい