Python.jp SlackからDiscordへ

これまで、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さんなんとか対応してくれないかなと願う次第だ。

個別の書き込みにURLが割り当てられていない

Slack では、それぞれの書き込みへのリンクを取得してあとから参照できるが、Discord にはこの機能がない。ちょっと離れた書き込みへのリプライなどは書きにくそうだ。

組み込みのBot機能が弱い

Discord には、SlackBot に相当する簡易Bot機能がない。また、GoogleTwitterなどと連携するbotもデフォルトでは用意されていないので、各サーバが自前で構築する必要がある。

絵文字の登録数に制限がある

Discord では最大50件しか登録できない。これだと、全ユーザに権限を開放して自由に登録できるようにするのは、ちょっと躊躇してしまう。

Reply機能がない

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) に Ubuntu 16.04 をインストールした

Dell XPS 13 (9370) の全部入り( i7-8550U・4Kタッチパネル・1TB SSD ・16 GBメモリ) を購入した。

これまでのところ、思ったより使い勝手がよい。キーボードはいい感じだし、パームレストのカーボンファイバも触り心地が良い。

液晶は非光沢がなくて光沢のみということでターミナルの背景におじさんが出現する不具合が怖かったが、あまり気にならない。手元のMacBook Airと比べても遜色ない気がする。

Linux環境でのタッチパッドも、設定でかなりまともに使えるようになった。 ← 後述のとおり、16.04ではダメだったので17.10に切り替えた

以下作業メモ

事前準備

  • どっちにしろWindows 10 Proをインストールするので、プリインストールのWindows 10 Homeは潔くパーティションごと削除。

  • UbuntuWindowsのインストール用USBを作成する。 XPS 13 (9370) では、Type-AのUSBメモリがいくつあっても無意味。Type-C - Type-A の変換アダプタを用意する。

  • Ubuntu 16.04もWindows 10も、インストールメディアで立ち上げた状態ではWiFiBluetoothが使えないので、USB Type-C で使えるテザリングかLanアダプタを用意する。

Ubuntuのインストール

XPS 13を再起動し、Dellのロゴが表示される前にF2キーを連打し、BIOS設定画面を表示する。

BIOS画面で、

  1. SATA-controllerをRaid -> AHCI とする。
  2. SecureBoot をオフにする

UbuntuUSBメモリを挿し、再起動する。Dellのロゴが表示される前にF12キーを押し、ブートデバイスにUSBを選択する。

Ubuntu 16.04インストールメディアに含まれているドライバでは、XPS 13のBluethoothやWiFi、画面のスケール設定などが動作しない。

とりあえず 16.04 をインストールし、LanアダプタやスマホのUSBテザリングなどを使って sudo apt update; sudo apt upgrade して再起動すれば正常に動作する。

libinput

[追記] 以下の手順で16.04にlibinputをインストールすると、ログインーログアウトを繰り返すとハングする模様。。。

デフォルトの状態ではタッチパッドpalm detectionまじ使えないので、次のページを参考に libinput をインストールする。

askubuntu.com

$ sudo apt install xserver-xorg-input-libinput

/usr/share/X11/xorg.conf.d/40-libinput.conf

# 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で行えないので、次のページを参考に設定を行う。

github.com

.profile

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のインストール

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がブートしてしまうので、Ubuntugrubでブートする場合は、UbuntuBoot-repair をインストールして設定を変更する。

https://askubuntu.com/questions/666631/how-can-i-dual-boot-windows-10-and-ubuntu-on-a-uefi-hp-notebook

hashlibのハッシュアルゴリズム、計算速度ってどのぐらい差があるの選手権

雑に図ってみた (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))

Python/CFFIでCUDAしてみる

ふと思い立って、CUDAでCPUからGPUにデータを転送する時の速度を測ってみた。

普通にCUDA SDKのサンプルで測定しても良いが、PythonCFFI で実行してみよう。

コードはこんな感じで書ける。

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

f:id:atsuoishimoto:20180130100853p:plain

ざっくり、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メモリより普通のメモリのほうが速いようだ。

f:id:atsuoishimoto:20180130100921p:plain

これは、データの転送よりも、転送の前処理・後処理に時間がかかってしまっているのかもしれない。ストリームを使って並列化するなどすればまた違ってくるかもしれないので、あとで実験してみたい。

クラスブロックのひみつ

さて、 リスト内包のひみつ - 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の名前解決ルール

Pythonspam*2 のような式の値を計算する時、Pythonは変数 spam の値を、次の順序で検索する。

  1. 式のスコープ。関数内で定義された入れ子の関数で実行していれば、親関数のスコープも検索する。

  2. 式のモジュールのグローバル変数

  3. 組み込み関数のモジュール __builtins__

例えば、

def foo():
    return 2 * spam

という関数を実行すると、Pythonは変数 spam

  1. 式を実行している foo() のローカル変数。
  2. foo() が所属するモジュールのグローバル変数
  3. __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 でちょっと話題になっていた。

次のようなコードだ

>>> a = [lambda: print(i) for i in range(3)]
>>> for i in a: i()
2
2
2

結論としては cocoatomo さんの書かれているように、変数の評価タイミングの問題で、

対処としては、次のように、出力する値を、関数の実行時ではなく、関数の作成時に決定する必要がある。

>>> a = [lambda x=i: print(x) for i in range(3)]
>>> for i in a: i()
0
1
2

もともとの問題はこれで解決するのだが、このコード、単純だがPython2とPython3では動作が異なっている。

Pythonの動作を理解するのに良い教材だと思うので、ちょっと解説してみよう。

Python2の場合

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の場合

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の関数のなかに別の関数を作成した時、親となる関数のローカル変数を子となる関数が参照する仕組みのことだ。

クロージャの仕組みは、以前

atsuoishimoto.hatenablog.com

に書いた。

したがって、Python2のように、後から i の値を変更することはできない。

>>> a = [lambda: print(i) for i in range(3)]
>>> i = 10000000
>>> for u in a:u()
...
2
2
2

Python2/3の違い

Python2 と Python3 では、リスト内包の実現方法に上記のような違いがある。これは、Python2 では

>>> i = 10000
>>> a = [lambda: print(i) for i in range(3)]
>>> print(i)
3

のように、リスト内包式があると他のローカル変数を上書きしてしまい、わかりにくいという問題を解決するためだ。

この経緯は

python-history-jp.blogspot.jp

でも解説されているので、参照されたい