2011年6月11日に書いたスクリプトの観賞

2011年6月11日に書いたスクリプトの観賞

それって震災から3ヶ月、の日だ

今日は東日本大震災から4年、の日。防災訓練が各地で行われることであろう。決してタイムリーを狙ったわけではないのだけれど、2011年6月11日に書いたものを発掘し、なんとも妙な気分になってしまった。鑑賞会してみる。

正確には…

本当のことを言うと、最初に書いたのはさらに遡るはずで。というのもこのタイムスタンプ、おそらく自分ちのチームにお披露目するために整理した日、なんじゃなかったか、という微かな記憶がある。個人向けで使っていたのはもっと前、多分 2006~2009 年の間あたりだったのではなかったか。

ここ4年くらいで「初めて本格的に」Pythonに取り組んだので、もはや初心者とは言えないだけのことは出来る(少なくとも雑か本気かの力加減とそれによる「質」は自身で制御できる)けれど、その頃はまさしく「自分が自分だけが楽するために自分だけが使う」ための Python 使いに過ぎず、相当昔から(2000年くらいから)の付き合いの割には「ずっと初心者的」だった頃。

(ちなみに「Pythonの存在」を知ったのはそこから更に遡り、1996年。これは正確。Ruby も同時に知った感じだったよ。)

これね

後で説明するけれど、無意味な三部作になっている。まず「モジュール」。

getclip_putclip.py
 1 import sys
 2 import win32con
 3 from win32clipboard import *
 4 from pywin32_testutil import str2bytes # py3k-friendly helper
 5 
 6 def _remove_after_nul(s):
 7     idx = s.find('\x00')
 8     if idx >= 0:
 9         return s[:idx]
10     return s
11 
12 def get_clip(f=sys.stdout):
13     OpenClipboard()
14     try:
15         # CF_UNICODE text always gives unicode objects back.
16         got = GetClipboardData(win32con.CF_UNICODETEXT)
17         f.write(_remove_after_nul(got.encode('cp932')))
18 
19         # CF_OEMTEXT is a bytes-based format.
20         # got = GetClipboardData(win32con.CF_OEMTEXT)
21 
22     finally:
23         CloseClipboard()
24 
25 def put_clip(text):
26     OpenClipboard()
27     try:
28         EmptyClipboard()
29         text_bytes = str2bytes(text)
30         # Now set the Unicode value
31         SetClipboardData(win32con.CF_UNICODETEXT, text)
32     finally:
33         CloseClipboard()
34 
35 if __name__=='__main__':
36     if len(sys.argv) == 1:
37         put_clip(u"Hello from Python unicode")
38     else:
39         put_clip(sys.argv[1].decode('cp932'))
40     get_clip()

次に「メイン」が2つ。

getclip.py
1 import sys
2 import getclip_putclip
3 
4 if __name__=='__main__':
5     getclip_putclip.get_clip()
putclip.py
1 import sys
2 import getclip_putclip
3 
4 if __name__=='__main__':
5     if len(sys.argv) > 1:
6         getclip_putclip.put_clip(" ".join(sys.argv[1:]).decode('cp932'))
7     else:
8         getclip_putclip.put_clip("".join(sys.stdin.readlines()).decode('cp932'))

何するものかって、これは、「cygwinにはあってMSYSにはない getclip, putclip のもどき」である。要 PyWin32。

py2exe前提だったとはいえ

py2exeでexeにしてしまい、「Pythonなくても使えるぜ」と言いたかったのだ。作った道具では皆に幸せにしてあげたいけれど、(当時)「どこの馬の骨とも知れない得体の知れない Python なんてシロモノをインストールさせては可哀想だ」ぐらいの感じ…だったよ、そら。どんなにワタシ自身が「世界中での潜在ユーザは日本の人口より多くてもおかしくねぇんじゃね?」と当時から思っていたとしてもそんなことは「知らない人々」にとってはどうでも良い事…だったわけである。

そんな事情もあって、「無意味な三部作」は成り立っていた。だって exe になっちまえば、getclip.exe、putclip.exe にしか見えんからね、中がダサかろうと関係ない。(当たり前だが作った当時は当人は自信満々である ;) )

最低でもこうだよなぁ

getclip.py または putclip.py
 1 import sys
 2 import win32con
 3 from win32clipboard import OpenClipboard
 4 from win32clipboard import GetClipboardData
 5 from win32clipboard import EmptyClipboard
 6 from win32clipboard import CloseClipboard
 7 from pywin32_testutil import str2bytes # py3k-friendly helper
 8 
 9 def _remove_after_nul(s):
10     idx = s.find('\x00')
11     if idx >= 0:
12         return s[:idx]
13     return s
14 
15 def get_clip(f=sys.stdout):
16     OpenClipboard()
17     try:
18         # CF_UNICODE text always gives unicode objects back.
19         got = GetClipboardData(win32con.CF_UNICODETEXT)
20         f.write(_remove_after_nul(got.encode('cp932')))
21 
22         # CF_OEMTEXT is a bytes-based format.
23         # got = GetClipboardData(win32con.CF_OEMTEXT)
24 
25     finally:
26         CloseClipboard()
27 
28 def put_clip(text):
29     OpenClipboard()
30     try:
31         EmptyClipboard()
32         text_bytes = str2bytes(text)
33         # Now set the Unicode value
34         SetClipboardData(win32con.CF_UNICODETEXT, text)
35     finally:
36         CloseClipboard()
37 
38 if __name__ == '__main__':
39     import os
40     if os.path.basename(sys.argv[0]).startswith("getclip"):
41         get_clip()
42     else:
43         put_clip(sys.argv[-1].decode('cp932'))

sys.argv[0] で振る舞いを変えるのは定石であろう。それこそ lnでハードリンクしておけば良い。

py2exe にするしないにも影響しないしな。

「sys.argv[0] で振る舞いを変えるのは定石」を知らなかったわけじゃないのにな、何トチ狂ったんだろ? (Unix C プログラムでは学生時代から多用してたもの。知らなかったわけがない。)

unicodeの扱い、理解できてるようで出来てない

「cp932」と決め打っちゃダメ。多分 sys.stdout.encoding 使うのがいいだろうね。

何見て作ったのだろう?

どうにも書かれてる英語コメントがやたらに「ネイティブ」くさいので気になって、周辺漁ってみたのだが、どうも PyWin32 のソースコード配布物に含まれるDemosを参考にしたように思える:

pywin32-219/win32/Demos/win32clipboardDemo.py
  1 # win32clipboardDemo.py
  2 #
  3 # Demo/test of the win32clipboard module.
  4 from win32clipboard import *
  5 from pywin32_testutil import str2bytes # py3k-friendly helper
  6 import win32con
  7 import types
  8 
  9 if not __debug__:
 10     print "WARNING: The test code in this module uses assert"
 11     print "This instance of Python has asserts disabled, so many tests will be skipped"
 12 
 13 cf_names = {}
 14 # Build map of CF_* constants to names.
 15 for name, val in win32con.__dict__.items():
 16     if name[:3]=="CF_" and name != "CF_SCREENFONTS": # CF_SCREEN_FONTS==CF_TEXT!?!?
 17         cf_names[val] = name
 18 
 19 def TestEmptyClipboard():
 20     OpenClipboard()
 21     try:
 22         EmptyClipboard()
 23         assert EnumClipboardFormats(0)==0, "Clipboard formats were available after emptying it!"
 24     finally:
 25         CloseClipboard()
 26 
 27 def TestText():
 28     OpenClipboard()
 29     try:
 30         text = "Hello from Python"
 31         text_bytes = str2bytes(text)
 32         SetClipboardText(text)
 33         got = GetClipboardData(win32con.CF_TEXT)
 34         # CF_TEXT always gives us 'bytes' back .
 35         assert  got == text_bytes, "Didnt get the correct result back - '%r'." % (got,)
 36     finally:
 37         CloseClipboard()
 38 
 39     OpenClipboard()
 40     try:
 41         # CF_UNICODE text always gives unicode objects back.
 42         got = GetClipboardData(win32con.CF_UNICODETEXT)
 43         assert  got == text, "Didnt get the correct result back - '%r'." % (got,)
 44         assert type(got)==types.UnicodeType, "Didnt get the correct result back - '%r'." % (got,)
 45 
 46         # CF_OEMTEXT is a bytes-based format.
 47         got = GetClipboardData(win32con.CF_OEMTEXT)
 48         assert  got == text_bytes, "Didnt get the correct result back - '%r'." % (got,)
 49 
 50         # Unicode tests
 51         EmptyClipboard()
 52         text = u"Hello from Python unicode"
 53         text_bytes = str2bytes(text)
 54         # Now set the Unicode value
 55         SetClipboardData(win32con.CF_UNICODETEXT, text)
 56         # Get it in Unicode.
 57         got = GetClipboardData(win32con.CF_UNICODETEXT)
 58         assert  got == text, "Didnt get the correct result back - '%r'." % (got,)
 59         assert type(got)==types.UnicodeType, "Didnt get the correct result back - '%r'." % (got,)
 60 
 61         # Close and open the clipboard to ensure auto-conversions take place.
 62     finally:
 63         CloseClipboard()
 64 
 65     OpenClipboard()
 66     try:
 67 
 68         # Make sure I can still get the text as bytes
 69         got = GetClipboardData(win32con.CF_TEXT)
 70         assert  got == text_bytes, "Didnt get the correct result back - '%r'." % (got,)
 71         # Make sure we get back the correct types.
 72         got = GetClipboardData(win32con.CF_UNICODETEXT)
 73         assert type(got)==types.UnicodeType, "Didnt get the correct result back - '%r'." % (got,)
 74         got = GetClipboardData(win32con.CF_OEMTEXT)
 75         assert  got == text_bytes, "Didnt get the correct result back - '%r'." % (got,)
 76         print "Clipboard text tests worked correctly"
 77     finally:
 78         CloseClipboard()
 79 
 80 def TestClipboardEnum():
 81     OpenClipboard()
 82     try:
 83         # Enumerate over the clipboard types
 84         enum = 0
 85         while 1:
 86             enum = EnumClipboardFormats(enum)
 87             if enum==0:
 88                 break
 89             assert IsClipboardFormatAvailable(enum), "Have format, but clipboard says it is not available!"
 90             n = cf_names.get(enum,"")
 91             if not n:
 92                 try:
 93                     n = GetClipboardFormatName(enum)
 94                 except error:
 95                     n = "unknown (%s)" % (enum,)
 96 
 97             print "Have format", n
 98         print "Clipboard enumerator tests worked correctly"
 99     finally:
100         CloseClipboard()
101 
102 class Foo:
103     def __init__(self, **kw):
104         self.__dict__.update(kw)
105     def __cmp__(self, other):
106         return cmp(self.__dict__, other.__dict__)
107     def __eq__(self, other):
108         return self.__dict__==other.__dict__
109 
110 def TestCustomFormat():
111     OpenClipboard()
112     try:
113         # Just for the fun of it pickle Python objects through the clipboard
114         fmt = RegisterClipboardFormat("Python Pickle Format")
115         import cPickle
116         pickled_object = Foo(a=1, b=2, Hi=3)
117         SetClipboardData(fmt, cPickle.dumps( pickled_object ) )
118         # Now read it back.
119         data = GetClipboardData(fmt)
120         loaded_object = cPickle.loads(data)
121         assert cPickle.loads(data) == pickled_object, "Didnt get the correct data!"
122 
123         print "Clipboard custom format tests worked correctly"
124     finally:
125         CloseClipboard()
126 
127 
128 if __name__=='__main__':
129     TestEmptyClipboard()
130     TestText()
131     TestCustomFormat()
132     TestClipboardEnum()
133     # And leave it empty at the end!
134     TestEmptyClipboard()

いつものワタシだ、とは思った。ソース配布物の Demo、Sample、test をほじくることから始めるから、アタシ。

それ見て作ったのなら

テキストだけで満足せずに、もちっと頑張って「画像なら画像ファイルに」とかすりゃ良かったのに、と思った。cygwin 相当だけで満足したんだろうなぁ。

鑑賞会おしまい、以後蛇足

Windows の「クリップボード API」は、信じられないかもしれないが「Win16時代からそのまま」である。無論 Win32 に刷新された際に「HGLOBAL/HLOCAL」の真に意味するところは(裏方では)変わったのだが、API の見かけは変わらなかったものだから、Windows プログラマはクリップボードを扱う際に非常に異質なプログラミングを要求され、苦痛だった。いくつかそういうのがあるんだよねぇ。今でも同じプログラムが通用するのがビックリするわ、ほんと。

何を言いたいの?

別にダメ人間自慢したいわけじゃないよ。

なんかね、「Python 教えて」みたいな検索する人がいるみたいなんだけれど、あたしゃね、「目の前に大量に転がっている「公式サンプル」」を見ることもなく「Python 教えて」ってなんなんだ、と思ったりもするわけでさ。「コードスニペット」はこういうブログみたいなのが相応しかったりもするんだけれど、「完全に動作するちゃんとしたお手本」て、「ソースコードを入手する」ことで簡単に手に入るぞ。バイナリばっか持ってこないで、一度はソースコード配布物、眺めてみりゃぁいい。(ま、Python 本体に限っては、そもそも「公式サイトのドキュメントを訪れもせずに「Python 教えて」ってなんだよ、と思う。チュートリアル読め。まずは。)

そんなスタンスは、あたしはあんまし変わってないんだなぁ、としみじみ思った。