pythonの関数中でグローバル変数に代入する

前の記事のフィボナッチ数列の実装の中で、関数が呼び出された回数を数えるために少し仕掛けをいれました。
参考:pythonの関数をメモ化する

で、その中で globalと言うのを使っているのでその解説です。

ドキュメントはこちら。
global文

pythonでは、関数の中でグローバル変数を”参照”することができます。
しかし、そのままではグローバル変数に”代入”はできません。

やってみましょう。
まずは、”参照”ができることの確認。


a = 1


def sample1():
    # グローバル変数aの値を参照できるためaの値1を戻す。
    return a


sample1()  # 1

次に、代入を試してみます。ちょっとわかりにくいですが、グローバル変数bを用意し、
関数中でbに代入していますがこのbが実はローカルの変数で、
関数の外でもともと宣言されていたbに影響していないことがわかります。


b = 2


def sample2():
    # グローバル変数bへの代入はできないので、このbはローカル変数
    b = 3
    print(b)


sample2()  # 3
# 変数bは変更されていない
print(b)  # 2

これが、global文を使うと、グローバル変数への代入が可能になります。


c = 10


def sample3():
    global c
    c = 20
    print(c)


sample3()  # 20
# 関数中の変更が反映されている
print(c)  # 20

pythonの関数をメモ化する

プログラムを書いていると再帰呼び出しする関数や整数値を引数にとる関数など、同じ値を渡して何度も実行される関数があります。
そのような場合、メモリーが十分あるのであれば毎回毎回処理を実行するより結果を保存しておく方が効率的です。

それを、メモ化(Memoization)と言うそうです。
メモ化 – Wikipedia

自分で実装するのも難しくないですが、pythonでメモ化を行うには、
@functools.lru_cache
と言う便利なデコレーターが用意されています。

ちなみに、lruは、Least Recently Usedの略です。
キャッシュアルゴリズム – Wikipedia

フィボナッチ数列で実際に使ってみましょう。
また、実際に効率化できている(関数中の処理の実行回数が減っている)ことを確認するために、
その関数が何回呼び出されたのかを記録し、表示します。
また、実行時間も測りましょう。

まず、メモ化をしない例。(jupyter notebookで動作させることを前提としたコードです。)


%%time
fib_count = 0  # 関数が呼び出された回数記録用


def fib(n):
    global fib_count
    fib_count = fib_count + 1
    if n <= 0:
        return 0
    elif n == 1:
        return 1
    else:
        return fib(n-1) + fib(n-2)


print("F(100)=", fib(30))
print("実行回数:", fib_count)

# 以下出力
F(100)= 832040
実行回数: 2692537
CPU times: user 449 ms, sys: 3.35 ms, total: 453 ms
Wall time: 456 ms

fibが何度も繰り返し実行され、非常に無駄の多い実装になっていることがわかります。

続いて、lru_cacheでメモ化した例です。


%%time
from functools import lru_cache
fib_memo_count = 0  # 関数が呼び出された回数記録用


@lru_cache(maxsize=None)
def fib_memo(n):
    global fib_memo_count
    fib_memo_count = fib_memo_count + 1
    if n <= 0:
        return 0
    elif n == 1:
        return 1
    else:
        return fib_memo(n-1) + fib_memo(n-2)


print("F(100)=", fib_memo(30))
print("実行回数:", fib_memo_count)

# 以下出力
F(100)= 832040
実行回数: 31
CPU times: user 229 µs, sys: 120 µs, total: 349 µs
Wall time: 311 µs

こんどのfib_memoは31回しか実行されませんでした。
n=0,...,30 でそれぞれ1回ずつですね。
処理時間も桁違いに早くなっています。

チェビシェフの不等式

たまには何か統計学の話題をだそう、と言うことでチェビシェフの不等式を紹介します。

確率変数$X$に対して、$\mu=E(X)$, $\sigma^2=V(X)$とすると、次の不等式が成立します。
$$
P(|X-\mu|\geq k\sigma) \leq \frac{1}{k^2}
$$

この式の便利さはどんな確率変数についても成立する点にあります。

もっとも、確率変数が従う分布がわかっていれば、これよりはるかに正確に見積もることができます。
ただ、実際の運用上、これは正規分布だとかこれは一様分布だとか何かしら保証されていることは稀で、
正確な分布はわからないけど、標本平均と標本分散だけは取れてて、
平均から大きく外れる確率をざっくり見積もりたいと言う場面は実際にあるのでそのような時に使えます。
(分布の大まかな形から正規分布だとか仮定してしまうことも多いのですが。)

参考文献 : 基礎統計学Ⅰ 統計学入門 (東京大学出版会) P104 5.4 チェビシェフの不等式

ディレクトリやファイルの権限を一括で修正する

職場で使っている端末の調子が悪く、新端末に交換することになったので大量のファイルを移行しました。
その際、一度NASにファイルを移して持って行ったのですが、
なぜかファイルやディレクトリの権限が全部 777 になってしまうと言う面倒な事態になりました。

chmod で一つ一つ直すのが面倒だったので一括で修正するコマンドがわかったので紹介します。

前提として、カレントディレクトリ”.”配下のすべてのファイルとディレクトリを対象に、
ファイルは644、ディレクトリは755に設定するには次のコマンドを実行します。
カレント以外を対象にする場合は、”.” の代わりにそのPathを入力したらできます。


find . -type d -exec chmod 755 {} +
find . -type f -exec chmod 644 {} +

一応解説。
find . -type d
と、
find . -type f
はそれぞれ、カレントディレクトリ配下の ディレクトリとファイルの一覧を取得します。
そして、 -exec はその後ろに書かれたコマンドを{}を find で見つけたpathに置換して実行します。
+ をつけておくと、各ファイル/ディレクトリに対してコマンドを個別に実行するのではなく、
まとめて実行してくれます。

詳細は man find で、マニュアルを読むのが確実です。

jupyter notebookからトレジャーデータに接続する

最近まで pandas-td を使っていましたが、 トレジャーデータのかたから pytd というライブラリを教えていただきましたのでその紹介です。
pytdのリポジトリ

インストールは pipできます。


pip install pytd

また、準備として環境変数の TD_API_KEY に APIキーをセットしておきましょう。
(write onlyではなく マスターKeyを。)

README.md には connect を使って接続して、queryを発行するサンプルコードが乗っていますが、
個人的にはマジックファンクションを使う方が断然お勧めです。


%load_ext pytd.pandas_td.ipython

とすると、 pandas_td と互換のマジックファンクションが使えるようになります。
(README に %%load_ext pytd.pandas_td.ipython って乗ってますが、これだと%が多くてエラーになります)


%%td_presto {データベース名} -o {結果の格納先変数名}
{ここにSQLを書く}

と言う構文で、jupyterでSQLを実行でき、しかも結果が pandas のデータフレームに格納されるので非常にスムーズに
以降の分析に入ることができます。

pandas-td から移行したばかりなのですが、心なしかpandas-tdより動作が速いのもいいですね。

pythonで集合(set)の包含関係を判定する

実は最近まで知らなかったのですが、pythonの集合(set)同士の包含関係を不等号で確認することができます。

ドキュメントはこちら
組み込み型 set(集合)型

念のため軽く用語の説明をしておくと、
集合Aと集合Bに対して、
Aの任意の要素xがBの要素でもある時、AはBの部分集合(subset)であるといいます。
それを記号で、 $A\subseteq B$ と書きます。
左右反転して、 $B\supseteq A$ と書くこともできます。
また、さらに集合Aと集合Bが等しくない時は、真部分集合(もしくは狭義の部分集合)といい、
$A\subset B$ もしくは $B\supset A$と書きます。

これをpythonではそれぞれ、 <=, >=, <, >, で計算できます。
戻り値はTrueかFalseです。
いくつかやってみましょう。


{1, 3, 5} <= {1, 2, 3, 4, 5}
# True

{1, 3, 5, 7} <= {1, 2, 3, 4, 5}
# False

{1, 3, 5} >= {1, 3, 5}
# True

{1, 3, 5} > {1, 3, 5}
# Flase

ハミング距離(Hamming distance)

二つの文字列がどのくらい異なるかを表す距離関数として、以前レーベンシュタイン距離と言うのを紹介しました。
参考:pythonで編集距離(レーベンシュタイン距離)を求める

これよりももっとシンプルな関数として、ハミング距離(Hamming distance)と言うのがあるのでその紹介です。
これは文字数が同じ二つの文字列に対して、単純に異なる文字を数えたものです。
ハミング距離 – Wikipedia

自分の場合、文字数が同じ時しか定義できないなどの理由により、レーベンシュタイン距離に比べて使う頻度は低いです。
ただ、文字数が同じでさえあれば高速に計算できます。

pythonでの実装ですが、レーベンシュタイン距離の時に使った、
python-Levenshtein に含まれています。

試しにやってみましょう。


import Levenshtein
print(Levenshtein.hamming("toned", "roses"))
# 3

この例の toned と roses では、ハミング距離もレーベンシュタイン距離もどちらも3ですが、
文字数が同じであってもこの2種類の距離の値が異なる例はあります。

例えば次のようなケースです。


text1 = 'abcdefg'
text2 = 'bcdefga'
print(Levenshtein.distance(text1, text2))
# 2
print(Levenshtein.hamming(text1, text2))
# 7

Macでlsコマンドの出力結果の文字色を変更する

今回もMacの小ネタ。
Macのターミナルでlsコマンドを使うと、ディレクトリに色を塗ってくれますが、背景色との組み合わせによっては見辛いことがあります。
(具体的に言えば、黒背景で使っている時の通常ディレクトリの青は見辛いです)

これらの色は、 LSCOLORS という環境変数に適切に値を設定することで変更可能です。
設定内容は、 ls コマンドのマニュアル内で検索すると出てきます。

$ man ls
で、マニュアルを開いて、
/LSCOLORS
で検索しましょう。

自分のMacで実行した時の該当部分を載せておきます。

LSCOLORS The value of this variable describes what color to use for which attribute when col-
ors are enabled with CLICOLOR. This string is a concatenation of pairs of the format
fb, where f is the foreground color and b is the background color.

The color designators are as follows:

a black
b red
c green
d brown
e blue
f magenta
g cyan
h light grey
A bold black, usually shows up as dark grey
B bold red
C bold green
D bold brown, usually shows up as yellow
E bold blue
F bold magenta
G bold cyan
H bold light grey; looks like bright white
x default foreground or background

Note that the above are standard ANSI colors. The actual display may differ depend-
ing on the color capabilities of the terminal in use.

The order of the attributes are as follows:

1. directory
2. symbolic link
3. socket
4. pipe
5. executable
6. block special
7. character special
8. executable with setuid bit set
9. executable with setgid bit set
10. directory writable to others, with sticky bit
11. directory writable to others, without sticky bit

The default is “exfxcxdxbxegedabagacad”, i.e. blue foreground and default background
for regular directories, black foreground and red background for setuid executables,
etc.

初期設定はこれです。
exfxcxdxbxegedabagacad

この22文字が、2文字ずつ”文字色””背景色”のペアになっていて、マニュアル中の11種類に対応しています。
最初の ex が directory の配色で、 blue の文字と、デフォルトの背景色です。

これが読みにくいので、 g : cyan あたりに変更するには、
gxfxcxdxbxegedabagacad と設定すれば大丈夫です。

.bash_profile に以下のように設定しておきましょう。


export LSCOLORS=gxfxcxdxbxegedabagacad

濃い青が明るい水色になって(僕の環境では)とても読みやすくなります。

Note that the above are standard ANSI colors. The actual display may differ depending on the color capabilities of the terminal in use.
と注意されている通り、環境によって色は違うので設定を変更するときは試しながら行うことをお勧めします。
例えば今の僕の環境では d : brown は黄色いです。

Mac の起動時刻を調べる

先日分け合って、使っているMacを何時に起動したのか調べる必要が発生しました。

結論から言うと、次のコマンドで調べることができます。
rebootなのに再起動だけでなく通常の起動もわかります。


$ last reboot
# --- 略 ---
reboot    ~                         Tue Jan  1 17:13
reboot    ~                         Thu Nov 29 19:28
reboot    ~                         Sun Nov 25 12:47
reboot    ~                         Thu Nov 22 14:51
reboot    ~                         Wed Nov 21 20:18

また、シャットダウン時刻を調べたい時は以下のコマンドです。 (出力略)


$ last shutdown

last とだけ打つと両方同時に表示されます。

もっと細かい使い方はそのうち調べようと想うのですが、
取り急ぎ今回の要件は満たしたのでメモしておきました。

pythonのfilter関数の使い方

前回の記事がmap関数の話だったので、今回は使い方のよく似たfilter関数です。

ドキュメントはこちら。
組み込み関数

基本的な構文は以下の形で、iterable の各要素に functionを適用して、
結果が新なものだけを取り出せます。
filter(function, iterable)

map関数と同様に、戻り値はリストではなくイテレーターになるので最初は少し戸惑いました。
試しに、整数のうち偶数だけ抽出するフィルターを書いてみます。


def f(n):
    return n % 2 == 0


m = filter(f, range(10))
print(m)
# <filter object at 0x114d178d0>
print(list(m))
# [0, 2, 4, 6, 8]
print(list(m))
# []

細かい説明は mapの記事に書いた通りなのですが、
イテレーターを使うために、リストに変換するなり、nextで取り出すなりする必要があり、
一度取り出すともう一回listに変換しようとしても空のリストしか返ってこなくなります。

なお、内包表記でもほぼ同じ処理が実装でき、自分はこちらを使うことが多いです。


[x for x in range(10) if f(x)]
[0, 2, 4, 6, 8]