cygwinで日本語を含むutf-8ファイルをクリップボードにコピーする方法

cygwinで日本語を含むutf-8ファイル(例えばaaa.txt)をクリップボードにコピーするには以下のコマンドを使います。

$ cat aaa.txt > /dev/clipboard

以下のコマンドだと文字化けしてしまうようです。

$ clip < aaa.txt

Python 3で絵文字を羅列する

Python3を使って、Unicodeに登録された絵文字を羅列して遊んでみました。この記事ではPython 3でのみ動作確認をしています。

コードポイントから文字への変換

Pythonの組み込み関数であるchr()を使うと、Unicodeのコードポイント(日本語では符号点)から文字への変換ができます。

import unicodedata
print(chr(97)) # 'a'
print(chr(12354)) # 'あ'

Unicodeのコードポイントは現時点でU+10FFFFまで存在するので、これを総当りするとすべての文字が羅列できます。

for i in range(0, 0x110000):
    ch = chr(i)

カテゴリ別に文字を分類

標準ライブラリであるunicodedataを使うと、文字の名前やカテゴリーなどを取得できます。

import unicodedata
print(unicodedata.name('あ'))  # 'HIRAGANA LETTER A'
print(unicodedata.category('あ')) # 'Lo'

カテゴリーの説明は例えば Unicode Character Categories で確認できます。'Lo'カテゴリーは'Letter, Other'という意味だそうです。

カテゴリー別の文字数を以下のコードで数え上げることができます。対した量ではないので数秒で完了します。

from collections import defaultdict
import unicodedata

# reference: http://www.fileformat.info/info/unicode/category/index.htm 
category_meanings = {
    'Cc' : 'Other, Control',
    'Cf' : 'Other, Format',
    'Cn' : 'Other, Not Assigned (no characters in the file have this property)',
    'Co' : 'Other, Private Use',
    'Cs' : 'Other, Surrogate',
    'LC' : 'Letter, Cased',
    'Ll' : 'Letter, Lowercase',
    'Lm' : 'Letter, Modifier',
    'Lo' : 'Letter, Other',
    'Lt' : 'Letter, Titlecase',
    'Lu' : 'Letter, Uppercase',
    'Mc' : 'Mark, Spacing Combining',
    'Me' : 'Mark, Enclosing',
    'Mn' : 'Mark, Nonspacing',
    'Nd' : 'Number, Decimal Digit',
    'Nl' : 'Number, Letter',
    'No' : 'Number, Other',
    'Pc' : 'Punctuation, Connector',
    'Pd' : 'Punctuation, Dash',
    'Pe' : 'Punctuation, Close',
    'Pf' : 'Punctuation, Final quote (may behave like Ps or Pe depending on usage)',
    'Pi' : 'Punctuation, Initial quote (may behave like Ps or Pe depending on usage)',
    'Po' : 'Punctuation, Other',
    'Ps' : 'Punctuation, Open',
    'Sc' : 'Symbol, Currency',
    'Sk' : 'Symbol, Modifier',
    'Sm' : 'Symbol, Math',
    'So' : 'Symbol, Other',
    'Zl' : 'Separator, Line',
    'Zp' : 'Separator, Paragraph',
    'Zs' : 'Separator, Space'
}

category_to_chars = defaultdict(list)
for i in range(0, 0x110000):
    ch = chr(i)
    category = unicodedata.category(ch)
    category_to_chars[category].append(ch)

for category in sorted(category_to_chars, key=lambda x: -len(category_to_chars[x])):
    print("{} ({}): {}".format(
        category,
        category_meanings[category],
        len(category_to_chars[category])))

結果は以下の通りです。

Cn (Other, Not Assigned (no characters in the file have this property)): 864409
Co (Other, Private Use): 137468
Lo (Letter, Other): 97553
So (Symbol, Other): 4404
Cs (Other, Surrogate): 2048
Ll (Letter, Lowercase): 1751
Lu (Letter, Uppercase): 1441
Mn (Mark, Nonspacing): 1281
Sm (Symbol, Math): 948
No (Number, Other): 464
Nd (Number, Decimal Digit): 460
Po (Punctuation, Other): 434
Mc (Mark, Spacing Combining): 352
Lm (Letter, Modifier): 237
Nl (Number, Letter): 224
Cf (Other, Format): 145
Sk (Symbol, Modifier): 115
Ps (Punctuation, Open): 74
Pe (Punctuation, Close): 73
Cc (Other, Control): 65
Sc (Symbol, Currency): 49
Lt (Letter, Titlecase): 31
Pd (Punctuation, Dash): 23
Zs (Separator, Space): 17
Pi (Punctuation, Initial quote (may behave like Ps or Pe depending on usage)): 12
Me (Mark, Enclosing): 12
Pc (Punctuation, Connector): 10
Pf (Punctuation, Final quote (may behave like Ps or Pe depending on usage)): 10
Zp (Separator, Paragraph): 1
Zl (Separator, Line): 1

絵文字を含む文字を羅列

上記カテゴリーのうち、'So (Symbol, Other)'カテゴリーはEmoji - Wikipediaを含むなどもっともカオスで見応えがあるので、このカテゴリーの文字を羅列してみます。上記のコードに続いて以下のコードを加えます。文字化けのしにくいChromeなどのブラウザでの表示を期待して、50文字ごとに改行タグを挿入しています。

CHAR_PER_LINE = 50
for i, ch in enumerate(category_to_chars['So']):
    print(ch, end="")
    if (i + 1) % CHAR_PER_LINE == 0:
        print("<br>")

一部のスクリーンショットを以下に示します。ドミノ、麻雀牌、トランプ、野球記号などが入り乱れていて驚きの連続です。 f:id:minus9d:20161208230332p:plain 出力結果の全部はunicode 'So' categoryで確認できます。

表形式での表示

unicodedata.name()を使うと文字の名前を知ることができます。以下のコードは、文字とその名前をテーブルで出力します。

print("<table border=\"1\">")
print("<tr><td>Char<td>Code Point<td>Name</tr>")
for ch in category_to_chars['So']:
    print("<tr><td>{}<td>{}(U+{:X})<td>{}</tr>".format(
        ch, ord(ch), ord(ch), unicodedata.name(ch)))
print("</table>")

部分を抜き出すと、以下のような感じで表示できます。

Char Code Point Name
9848(U+2678) RECYCLING SYMBOL FOR TYPE-6 PLASTICS
9849(U+2679) RECYCLING SYMBOL FOR TYPE-7 PLASTICS
9850(U+267A) RECYCLING SYMBOL FOR GENERIC MATERIALS
9851(U+267B) BLACK UNIVERSAL RECYCLING SYMBOL
9852(U+267C) RECYCLED PAPER SYMBOL
9853(U+267D) PARTIALLY-RECYCLED PAPER SYMBOL
9854(U+267E) PERMANENT PAPER SIGN
9855(U+267F) WHEELCHAIR SYMBOL
9856(U+2680) DIE FACE-1
9857(U+2681) DIE FACE-2

ブラウザに表示されるテーブルをコピペしてunicode 'So' category tableに載せました。

コード全文

unicode_bruteforce.py

Pythonの例外処理に関するまとめ

適当に書いてしまいがちな例外処理について自分なりにまとめました。

Pythonにおける例外

Pythonでは「認可をとるより許しを請う方が容易 (easier to ask for forgiveness than permission)」、略してEAFPというコーディングスタイルが推奨されています。EAFPは、エラーを起こすかもしれない処理もまず実行してみて、もしエラーが出たらそのとき後始末をする、というスタイルです。従って、Pythonは比較的例外処理が使われやすい言語であると言えます。

基本的な文法

0除算エラーと型エラーを捕捉する例を以下に示します。

def func(val):
    try:
        res = 1 / val
    except ZeroDivisionError:
        print("zero division error")
    except TypeError:
        print("type error")
    else:
        print("1 / {} = {}.".format(val, res))
    finally:
        print("end of func()")

func(1)
func(0)
func(None)

各節の意味は以下の通りです。

  • try節
    • 例外が発生する可能性があるコードを書くところです。
  • except節
    • 例外クラス名を指定し、捕捉する例外の種類を限定して捕捉します。何個も並べることもできます。
  • else節
    • try節内で例外が発生しなかった場合のみ実行される節です。
  • finally節
    • try節内で例外が発生したか否かに関わらず実行される節です。

実行結果は以下です。

1 / 1 = 1.0.
end of func()
zero division error
end of func()
type error
end of func()

例外オブジェクトを捕捉するにはasを使います。

try:
    1/0
except ZeroDivisionError as e:
    print(e)

実行結果は以下です。

division by zero

すべての例外を捕捉する

発生する例外の種類がわかっているときは、前節で書いたように、なるべく具体的に例外の種類を指定するのがよいとされています(要出典)。しかし実際には、どんな例外を発生するかわからないコードを呼び出す必要に迫られることがあります。このときには発生しうるすべての例外を捕捉したくなるでしょう。よく見かけるがあまりお勧めでない書き方を以下に示します。

try:
    dirty_func()
except:
    print("error!")

この書き方には2つの問題があると言えます。

  1. 捕捉したくない例外まで捕捉されてしまう
  2. 重要なエラーを揉み消してしまう

順番に見ていきます。

問題1. 捕捉したくない例外まで捕捉されてしまう

このコードの1つ目の問題は、Ctrl+CによるKeyboardInterruptまでをも捕捉してしまうことです(もちろん、それが意図したことならそれでよいのですが)。例えば以下のコード

sum = 0
while True:
    try:
        sum += 1
    except:
        print("error!")

を実行し、Ctrl+Cを押してみてください。ちょうどsum += 1の実行中にCtrl+Cを押すとそれが例外として捕捉されてしまうので、複数回Ctrl+Cを押さないと終了できないことがあると思います。

改善したコードを以下に示します。こうすると、KeyboardInterruptSystemExitなど捕捉したくないいくつかの例外を除いた(ほぼ)すべての例外が捕捉できます。

try:
    dirty_func()
except Exception as e:
    print(e)

この理由は5. 組み込み例外 — Python 3.5.2 ドキュメントを見ると分かります。このページのExceptionクラスの説明には「システム終了以外の全ての組み込み例外はこのクラスから派生しています。全てのユーザ定義例外もこのクラスから派生させるべきです」とあります。つまり、まともなライブラリが返す例外は、すえてExceptionクラスを継承していることが期待されるので、上記のコードでうまくいくわけです。

ただし、可能性は低いですが、Exceptionクラスを継承せずに作成した行儀の悪いユーザ定義例外を返されてしまうと、上記コードでは捕捉できません。例を以下に示します。

# 誤った例外クラス定義の方法! 正しくはExceptionクラスを継承してください
class MyError(BaseException):
    pass

try:
    raise MyError
except Exception as e:
    print("error!")

これを実行するとエラー捕捉に失敗することが確かめられます。このようなレアな現象まで考慮せざるを得ない場合は、except Exception:ではなくexcept:と書かないといけないはずです。

問題2. 重要なエラーを揉み消してしまう

例外をキャッチして適切な処理をせずそのまま揉み消すくらいなら何もしない方がマシなことがあります。例えば以下のコードは0から9までの和を計算するつもりのコードですが、実行すると0が表示されます。

ans = 0
for i in range(10):
    try:
        ams += i
    except Exception:
        pass
print(ans)

これは、ansをミススペルしたamsによりNameErrorが発生したのにも関わらず、それを揉み消してしまったせいで起こった悲劇です。プログラムは動作し続けるものの、どんな挙動になるか予想ができない危険な状態と言えます。

改善案として、except節にてraiseにより例外を投げ直すという手があります。コードを以下に示します。

ans = 0
for i in range(10):
    try:
        ams += i
    except Exception:
        raise
print(ans)

これを実行すると、以下のように例外が出てプログラムが停止します。

Traceback (most recent call last):
  File "error_test.py", line 9, in <module>
    ams += i
NameError: name 'ams' is not defined

このように、exceptチェーンの最後ではraiseと書いておくのがよいと思います。

どんな怪しい状態になってもいいからどにかくプログラムを停止させたくない、という場合ではraiseと書きたくないかもしれません。このような場合であっても、最低限なんらかのログを残しておくことくらいはしておくべきだと思います。

スタックトレースを表示

例外処理をするとスタックトレースが表示されなくなってしまうので、デバッグのときに不便です。例えば以下のコードを実行すると、division by zeroとしか表示されず、エラー箇所がわかりません。

def func1():
    1 / 0

def func2():
    return func1()

try:
    func2()
except Exception as e:
    print(e)

スタックトレースを表示するには、標準ライブラリのtracebackを使います。

import traceback

def func1():
    1 / 0

def func2():
    return func1()

try:
    func2()
except Exception as e:
    traceback.print_exc()

上記コードを実行すると、以下のように例外発生箇所が分かります。

Traceback (most recent call last):
  File "error_test.py", line 26, in <module>
    func2()
  File "error_test.py", line 23, in func2
    return func1()
  File "error_test.py", line 20, in func1
    1 / 0
ZeroDivisionError: division by zero

参考URLs

Pythonのvars()とdir()の違い

よくどっちがどっちだったか混乱してしまうので自分用にまとめます。最初に、引数にオブジェクトを渡して呼び出したときの挙動を比較したのち、引数なしで呼び出したときの挙動を比較します。

vars(obj)

2. 組み込み関数 — Python 3.5.2 ドキュメントによると

モジュール、クラス、インスタンス、あるいはそれ以外の dict 属性を持つオブジェクトの、 dict 属性を返します

とあります。例で確かめてみます。

class MyClass:
    def __init__(self):
        self.val1 = 10
        self.val2 = 20

obj = MyClass()
print(vars(obj))
print(obj.__dict__)

この出力は以下の通りです。vars(obj)は、objを辞書として扱ったときの値を返していると解釈できそうです。

{'val1': 10, 'val2': 20}
{'val1': 10, 'val2': 20}

dir(obj)

2. 組み込み関数 — Python 3.5.2 ドキュメントによると

そのオブジェクトの有効な属性のリストを返そうと試みます

とあります。以下は、先ほどのvars()をdir()で置換しただけのコードです。

class MyClass:
    def __init__(self):
        self.val1 = 10
        self.val2 = 20

obj = MyClass()
print(dir(obj))

この出力は以下の通りです。

['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'val1', 'val2']

このように、オブジェクトが持つ属性のみならず、オブジェクトが属しているクラスが持つ属性をも含んだリストが返ってきます。dir()関数の使用用途としては、プログラマが対話的にオブジェクトが持つ属性の一覧を覗いて、使えそうなものを探ることのようです。再び2. 組み込み関数 — Python 3.5.2 ドキュメントから引用します。

注釈 dir() は主に対話プロンプトでの使用に便利なように提供されているので、厳密性や一貫性を重視して定義された名前のセットというよりも、むしろ興味を引くような名前のセットを返そうとしま す。また、この関数の細かい動作はリリース間で変わる可能性があります。例えば、引数がクラスであるとき、メタクラス属性は結果のリストに含まれません。

vars()

引数なしでvars()を呼ぶと、「locals() のように振る舞います」とのこと。locals()は、「現在のローカルシンボルテーブルを表す辞書を更新して返します」とのこと。以下のコード

class MyClass:
    def __init__(self):
        self.val1 = 10
        self.val2 = 20

obj = MyClass()
print(vars())

の実行例は以下です。

{'__spec__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x00000289CD407940>, '__file__': '/path/to/somewhere.py', '__cached__': None, '__builtins__': <module 'builtins' (built-in)>, '__name__': '__main__', 'obj': <__main__.MyClass object at 0x00000289CD4698D0>, 'MyClass': <class '__main__.MyClass'>, '__doc__': None, '__package__': None}

dir()

引数なしでvars()を呼ぶと、「現在のローカルスコープにある名前のリストを返します。」とのこと。

class MyClass:
    def __init__(self):
        self.val1 = 10
        self.val2 = 20

obj = MyClass()
print(dir())

の実行例は以下です。

['MyClass', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'obj']

引数なしvars()の戻り値である辞書のkeys()と同じ結果のように見えます。

参考URL

PyCharmでエディタの文字サイズをCtrl + マウスホイールで変更可能にする

表題のようにする方法が PyCharm 2016.1 Help :: Zooming in the Editor にあります。 File -> Settings...と辿ってウィンドウを開いた後、左側のペインでEditor -> Generalと辿り、"Change font size (Zoom) with Ctrl+MouseWheel"にチェックを付ければOKです。 PyCharm Community Edition 2016.1 で確認しました。

pythonでwithによるネストを防ぐ

Pythonではファイルを開くときなどにwithを使うのが定石である(参考:ファイル - Dive Into Python 3 日本語版)。しかし、複数のファイルを開くときには以下のようにネストが発生してしまうのが気に入らなかった。

with open('a.txt', 'w') as f1:
    with open('b.txt', 'w') as f2:
        # 処理

Multiple variables in Python 'with' statement - Stack Overflow によると、以下のコードでネストを回避できる。Python 2.7とPython 3.1以降で利用可能らしい。

with open('a.txt', 'w') as f1, open('b.txt', 'w') as f2:
    # 処理

O'Reillyで買ったebooksをKindleで読む

現在O'ReillyでEbookがすべて半額セール中 (Ebooks - O'Reilly Media)なのを利用して、C in a Nutshell, 2nd Edition - O'Reilly Mediaを買ってみました。いつも購入したEbookをスマホで読めるようにする方法を忘れてしまうので、簡単にメモします。

管理画面

EbookをクレジットカードやPaypalで購入した後、"Your Account"、"Your Products"と順にクリックして購入したEbookを探す。

f:id:minus9d:20160912205345p:plain

Dropboxとの同期

"Send Ebook"、"Send To Dropbox"を順にクリックしてDropboxと紐付けることにより、Ebookが更新されたときに自動でDropbox上のEbookも更新される。

Kindleへの転送

Kindleで使える無料の英和辞書が素晴らしいため、Kindleで読むのが自分にとってはもっとも効率がよい。Amazonから"Send-to-Kindle Eメールアドレス"というのをもらっているはずなので、そのアドレスに.mobiファイルを添付して送るだけでよい。自分の"Send-to-Kindle Eメールアドレス"の探し方は、Amazon.co.jp ヘルプ: Send-to-Kindle Eメールアドレスの使い方についての「Send-to-Kindle Eメールアドレスの表示または変更」を参照。