以前書いてみたアクセサ をpropertyを使って改良してみる。

propertyというのはnew-styleクラスに対してのみ使える、メソッドで

1class C(object):
2    def __init__(self): self.__x = None
3    def getx(self): return self.__x
4    def setx(self, value): self.__x = value
5    def delx(self): del self.__x
6    x = property(getx, setx, delx, "I'm the 'x' property.")

こんな感じで使う。

Rubyを使った人なら分かると思うけど、プロパティ名でのアクセス=アクセサ呼び出しになるのは結構便利。 ActiveRecordなんかでは、特に便利だと感じる。あ、このプロパティ、もういっそエンコードされた値を返したい、と思ったときにそのクラスにプロパティ名と同じメソッドを定義するだけでいいし。

前につくったアクセサは「get_hoge()」や「set_hoge()」といった感じで呼び出されたけど、これにpropertyを使うことによってRubyと同じような属性アクセスが可能になる。

とりあえずこんな感じ。

 1#!/usr/bin/python
 2# vim: fileencoding=utf-8
 3from itertools import * 
 4
 5def property_accessor(cls, *names): 
 6  _property(cls, names)
 7
 8def property_reader(cls, *names):
 9  _property(cls, names, True)
10
11def property_writer(cls, *names):
12  _property(cls, names, False, True)
13
14def _property(cls, names, error_setter = False, error_getter = False):
15  for attr_name, doc in _name_and_docs(names):
16    real_name = _real_name(cls, attr_name)
17    setter  = _add_setter(cls, attr_name, real_name, error_setter)
18    getter  = _add_getter(cls, attr_name, real_name, error_getter)
19    deleter = _add_deleter(cls, attr_name, real_name)
20    setattr(cls, attr_name, property(getter, setter, deleter, doc))
21
22def _name_and_docs(name_or_tuples):
23  return imap(_name_and_doc ,name_or_tuples)
24
25def _name_and_doc(name_or_tupple):
26  if isinstance(name_or_tupple, ("".__class__, u"".__class__)) :
27    return (name_or_tupple, "")
28  else:
29    return name_or_tupple
30
31def _real_name(cls, name) :
32  return "_%s__%s"%(cls.__name__, name)
33
34def _add_method(cls, verb, name, method_to_add):
35  method_name = "%s_%s" % (verb, name)
36  method = cls.__dict__.get(method_name)
37  if not method :
38    method = method_to_add
39    setattr(cls, method_name, method)
40  return method
41
42def _add_setter(cls, name, real_name, error = False) :
43  if error:
44    def setter(self, v):
45      raise AttributeError, "class %s: %s is a read-only attribute." % (cls.__name__, name)
46  else:
47    setter = lambda self, v : setattr(self, real_name, v)
48  return _add_method(cls, "set", name, setter)
49
50def _add_getter(cls, name, real_name, error = False) :
51  if error:
52    def getter(self):
53      raise AttributeError, "class %s: %s is a write-only attribute." % (cls.__name__, name)
54  else:
55    getter = lambda self : getattr(self, real_name)
56  return _add_method(cls, "get", name, getter)
57
58def _add_deleter(cls, name, real_name) :
59  deleter = lambda self : delattr(self, real_name)
60  return _add_method(cls, "delete", name, deleter)
61
62class AccessorType(type):
63  def __new__(cls, class_name, class_bases, classdict):
64    cls = type.__new__(cls, class_name, class_bases, classdict)
65    list = ["__accessor__", "__reader__", "__writer__"]
66    methods = imap(lambda n: eval("property_%s"%n.strip("_")), list)
67    for accessor_type, method in izip(list, methods):
68        if classdict.has_key(accessor_type):
69          method(cls, *classdict[accessor_type])
70    return cls
71
72class Accessor(object):
73  __metaclass__ = AccessorType
74  def __init__(self, *args) :
75    list = ["__accessor__", "__reader__", "__writer__"]
76    for accessor_type in list:
77      if not self.__class__.__dict__.has_key(accessor_type) : continue
78      for name in self.__class__.__dict__.get(accessor_type):
79        attr_name, doc = _name_and_doc(name)
80        if not self.__dict__.get(attr_name):
81          exec("self._%s__%s = None" % (self.__class__.__name__, attr_name))

使い方は

 1#!/usr/bin/python
 2# vim: fenc=utf-8
 3import accessor
 4
 5class Test(accessor.Accessor) :
 6  __accessor__ = [("x", "This is x value"), "y", "z"]
 7  __reader__    = ["r"]
 8
 9  def __init__(self):
10    accessor.Accessor.__init__(self)
11
12  def get_y(self) :
13    return "new y value"
14
15obj = Test()
16obj.x = "hoge"
17
18print obj.x
19# => hoge
20
21print Test.x.__doc__
22# => This is x value
23
24print obj.y
25# => new y value
26
27obj.r = "value"
28# => AttributeError: class Test: r is a read-only attribute.

こんな感じ。

get_* とか set_* を定義すれば、独自のアクセサを定義できる。 属性は全て private(__*) として保存されているので、 self.__x みたいな感じでクラス内ではアクセスできる。

これやっててはまったのはココ。

1exec("self._%s__%s = None" % (self.__class__.__name__, attr_name))

いや self.__dict__[attr_name] = None でいいんだけど、ふとexecを使うと予想外なことがおこったのであえて記事にのっけてみた。

なんとこれじゃだめなのだ!!ふつうPythonでは self.__x = hoge とすると自動的に内部で self.__dict__["_classname__x"] = "hoge" に変換される。

でもexecでやるとどうやらこの過程をぶっ飛ばしてしまうらしい。要はprivateになってくれない。なので自分で変換してやることで解決する。

ところでこういうアクセサ生成メタクラスってネットにいっぱい転がってるんだけど、そろそろ標準で組み込まれたりしないんだろうかねえ。


Pythonのコードをwindows用exeにするには当然、 py2exe を使います。

で、そのとき、

1python setup.py py2exe --windows

とすれば、コンソールを表示しないようにできます。

これはもう古いやり方で、現在のバージョンのpy2exeでは動きません・・・。現在はターゲットファイルの拡張子を.pywにする、もしくは windows = [{'script' : 'script.py', "icon_resources": [(1,"script.ico")]}] というオプションをsetupに渡す、という方法になっています。

今日、昔py2exeで作ったファイルが出てきたんですが、困ったことにpythonのソースファイルはない。いや、別に改良とかもうしないしいいんだけど。 ふと起動してみるとGUI with console。かっちょわりい。

コンソールが表示される、というのは単純にPEファイルのオプションなわけで。

image

この部分を変更してやればとりあえずは直る。ちなみに、

  • 00 00:未知のサブシステム
  • 01 00:デバイス ライバおよびWindowsNTネイティブプロセス用
  • 02 00:GUIで実行されるファイル
  • 03 00:コンソールで実行されるファイル
  • 07 00:Posixコンソールで実行されるファイル
  • 09 00:WindowsCEで実行されるファイル

なので02h 00hにしてやればオッケー。

Pythonでバイナリファイルの読み書きとかしたことないので練習もかねて。

 1# vim: fileencoding=utf-8
 2import sys
 3from struct import * 
 4target_file = len(sys.argv) > 1 and sys.argv[1] or sys.exit("Target file is not specified.")
 5target_file = unicode(target_file, "mbcs")
 6out_file    = open(target_file + "_gui", "wb")
 7
 8io = open(target_file, "rb")
 9while 1 :
10  if io.read(1) == "P" and io.read(1) == "E" : break
11subsystem_pos = io.tell() +90 
12io.seek(0)
13out_file.write(io.read(subsystem_pos))
14io.seek(4, 1)
15out_file.write(pack('hh', 2, 0))
16out_file.write(io.read())
17
18io.close()
19out_file.close()

こんな感じ。引数にexeファイルを渡せばオッケー。でも、適当なので全部のケースで動くかはあやしいw pythonではバイナリを扱うときはstructモジュールを使う、ということが分かりました(笑


Windowsの小物はほぼPythonで作っているわけですが。

WindowsにPythonをインストーラーを使ってインストールすると*.pyにデフォルトだとpython.exeを関連付けしてくれます。 これは非プログラマな人にプログラムを渡すとき非常に便利で、とりあえずPythonをインストールして、このファイルをダブルクリックしろ、というだけでオッケー。exe化して無駄な容量を食わなくても大丈夫なのです。

んで、作るのはだいたいコンソールで実行するもの。 これがダブルクリックで実行できるのはいいんですが、当然、プログラムが終了するとウインドウ(DOSプロンプト)が閉じちゃうから結果が見れない。 いちいちDOSプロンプトから実行するのもめんどくさい。しかも見た目的に非プログラマにはいかつい。

なんとかならないかなー、と思ってTkを使ってコンソールアプリを作るためのライブラリを作ってみた。

 1# vim: fileencoding=utf-8
 2from Tkinter import *
 3from ScrolledText import ScrolledText
 4import sys
 5import thread
 6import time
 7
 8class GUIConsole(Frame):
 9  def init(self):
10    self.init_input()
11    self.init_output()
12
13  def init_input(self):
14    self.input_var = StringVar()
15    self.input = Entry(self, width=100, textvariable=self.input_var)
16    self.input.pack(side=TOP)
17    self.input.bind('<Return>', self.input_enter)
18
19    self.input_var.readline = self.readline
20    sys.stdin = self.input_var
21
22  def init_output(self):
23    self.out = ScrolledText(self, width=100, height=30)
24    self.out.pack(side=TOP)
25    self.out.write = self.write
26    sys.stdout = self.out
27
28  def write(self, str):
29    self.out.insert(END, str)
30    time.sleep(0.0001)
31    self.out.yview_scroll(str.count("\n") + 1, "units")
32
33  def readline(self, size=None):
34    self.input.focus()
35    self.input_entered = False
36    while True:
37      time.sleep(0.5)
38      if self.input_entered == True:
39        break
40    result = self.input_var.get()
41    self.input_var.set("")
42    return result
43
44  def input_enter(self, event):
45    self.input_entered = True
46
47  def __init__(self, title, master=None):
48    self.input_entered = False
49    Frame.__init__(self, master)
50    self.pack()
51    self.master.title(title)
52    self.init()
53
54def start(main_func, title="Python") : 
55  app = GUIConsole(title)
56  thread.start_new_thread(main_func, ())
57  app.mainloop()

使い方はこんなかんじ。

1import guiconsole
2
3def main():
4  while True:
5    var = raw_input()
6    print var
7
8guiconsole.start(main, "GUIコンソールのテスト")

でこんな風にみえる。

image

一番上に入力欄があって、その下に結果表示エリア。

Pythonの場合、stdoutはwriteというメソッド、stdinはreadlineというメソッドさえもっていればどんなオブジェクトでもオッケー。 なので単純にstdoutとstdinをTkのウィジットに置き換えてメインループに入り、別スレッドでメインの処理を実行してやっているだけ。

見栄えも生のDOSプロンプトよりはいいし、処理が終わってもウィンドウを閉じない限り結果を見ることができる。 プチ便利なので、はやくも自分で使いまくりです(笑