2009年9月30日水曜日

String concatenation in Python

文字列連結の効率の話
addのほうが早かったよなと思って、色々試してみた。

  1. def str_mul():  
  2.     return name * count  
  3.   
  4. def str_join():  
  5.     lst = []  
  6.     append = lst.append  
  7.     for i in xrange(count):  
  8.         append(name)  
  9.     return ''.join(lst)  
  10.   
  11. def str_map():  
  12.     lst = map(lambda x:name, xrange(count))  
  13.     return ''.join(lst)  
  14.   
  15. def list_comp():  
  16.     return ''.join(name for i in xrange(count))  
  17.   
  18. def str_add():  
  19.     s = ''  
  20.     for i in xrange(count):  
  21.         s += name  
  22.     return s  
  23.   
  24. import cStringIO  
  25. def cstring_io():  
  26.     io = cStringIO.StringIO()  
  27.     write = io.write  
  28.     for i in xrange(count):  
  29.         write(name)  
  30.     return io.getvalue()  
  31.   
  32. import StringIO  
  33. def string_io():  
  34.     io = StringIO.StringIO()  
  35.     write = io.write  
  36.     for i in xrange(count):  
  37.         write(name)  
  38.     return io.getvalue()  
  39.   
  40. from array import array  
  41. def str_array():  
  42.     a = array('c')  
  43.     add = a.fromstring  
  44.     for i in xrange(count):  
  45.         add(name)  
  46.     return a.tostring()  
  47.   
  48. from mmap import mmap  
  49. def str_mmap():  
  50.     m = mmap(-1, count * len(name))  
  51.     write = m.write  
  52.     for i in xrange(count):  
  53.         write(name)  
  54.     m.seek(0)  
  55.     return m.read(count * len(name))  
  56.   
  57. def main():  
  58.     global count, name  
  59.     func_list = (str_mul, str_join, str_add, string_io, cstring_io,  
  60.                  str_map, list_comp, str_array, str_mmap)  
  61.   
  62.     # test  
  63.     count = 2  
  64.     name = 'hello'  
  65.     assert all((name * count).__eq__(f()) for f in func_list)  
  66.   
  67.     import timeit  
  68.     for c in (101000):  
  69.         for b in (11000):  
  70.             for f in func_list:  
  71.                 count = c  
  72.                 name = 'hello' * b  
  73.                 print '%10s x %4s, "hello"*%4s: %9.4fms'%(  
  74.                     f.func_name, c, b, timeit.timeit(f, number=100)/100*1000)  
  75.   
  76. if __name__ == '__main__':  
  77.     main()  

   str_mul x   10, "hello"*   1:    0.0006ms
  str_join x   10, "hello"*   1:    0.0042ms
   str_add x   10, "hello"*   1:    0.0033ms
 string_io x   10, "hello"*   1:    0.0312ms
cstring_io x   10, "hello"*   1:    0.0072ms
   str_map x   10, "hello"*   1:    0.0059ms
 list_comp x   10, "hello"*   1:    0.0077ms
 str_array x   10, "hello"*   1:    0.0081ms
  str_mmap x   10, "hello"*   1:    0.0180ms
   str_mul x   10, "hello"*1000:    0.0108ms
  str_join x   10, "hello"*1000:    0.0131ms
   str_add x   10, "hello"*1000:    0.0126ms
 string_io x   10, "hello"*1000:    0.0411ms
cstring_io x   10, "hello"*1000:    0.0364ms
   str_map x   10, "hello"*1000:    0.0146ms
 list_comp x   10, "hello"*1000:    0.0167ms
 str_array x   10, "hello"*1000:    0.0377ms
  str_mmap x   10, "hello"*1000:    0.1105ms
   str_mul x 1000, "hello"*   1:    0.0017ms
  str_join x 1000, "hello"*   1:    0.2352ms
   str_add x 1000, "hello"*   1:    0.2235ms
 string_io x 1000, "hello"*   1:    2.4272ms
cstring_io x 1000, "hello"*   1:    0.5111ms
   str_map x 1000, "hello"*   1:    0.3591ms
 list_comp x 1000, "hello"*   1:    0.2296ms
 str_array x 1000, "hello"*   1:    0.5338ms
  str_mmap x 1000, "hello"*   1:    0.4778ms
   str_mul x 1000, "hello"*1000:    8.5971ms
  str_join x 1000, "hello"*1000:    8.3908ms
   str_add x 1000, "hello"*1000:   29.4590ms
 string_io x 1000, "hello"*1000:   10.6351ms
cstring_io x 1000, "hello"*1000:   28.7235ms
   str_map x 1000, "hello"*1000:    8.8407ms
 list_comp x 1000, "hello"*1000:    8.2300ms
 str_array x 1000, "hello"*1000:   38.9228ms
  str_mmap x 1000, "hello"*1000:   18.6053ms

addは50kB程度までは、文字列長や回数にかかわらず早い。
'str' * nをjoinが逆転するのは見てびっくり。
StringIOは1MBを超えたあたりで突然加速、理由不明。
cStringIOを余裕で追い抜き、100MBあたりではjoinに並ぶ。
ただし、1MBを超えるようなものはPythonで扱うべきではないかも。
for, map, list内包は勝ったり負けたり。
disってみたところ、早そうなのはfor-join。
psycoも使ってみたけど、速度はあまりかわらず。

結論:
addでもjoinでも扱いやすいほうを使おう。
速度はPythonを使う上では気にしない。

2009年9月14日月曜日

Interpolation surprise — And now for something completely Pythonic...

Interpolation surprise — And now for something completely Pythonic...

おそろしや
  1. class Surprise(object):  
  2.     def __str__(self):  
  3.         return "[str]"  
  4.     def __unicode__(self):  
  5.         return u"[unicode]"  
  6.   
  7. surprise = Surprise()  
  8.   
  9. print "%s %s %s" % (surprise, u"foo", surprise)  

出力:
[str] foo [unicode]

つまり、途中でUnicodeが来ると、それ以降は__unicode__が呼ばれるようになるわけです。


ちなみにPython2.3なら
出力:
[str] foo [str]
となります。

2009年9月3日木曜日

setuptools

SolacePlurkからリリースされました。

Stack Overflowのクローンです。
あいかわらず仕事が早いしソースが読みやすい。

しかし今回、最も驚いたのが、setup.pyの使い方。
こんな便利なものだったんですね。
なお、正しい手順はREADMEInstallationを見てください。

私の手順は以下のとおり。

$ hg clone http://bitbucket.org/plurk/solace
$ mkvirtualenv solace
(solace)$ cd solace
(solace)$ python setup.py develop
(solace)$ echo SECRET_KEY = \'`mkpasswd -l 40`\' > config.py
(solace)$ SOLACE_SETTINGS_FILE=config.py python setup.py reset
(solace)$ SOLACE_SETTINGS_FILE=config.py python setup.py runserver

はやい!本当に早い!
setup.pyってこんなに便利だったんだ。
拡張コマンドを追加できるなんて知らなかったよ!

さて、setup.pyを読んでいきます。
まずはここ。
  1. try:  
  2.     from solace import scripts  
  3. except ImportError:  
  4.     pass  
  5. else:  
  6.     extra['cmdclass'] = {  
  7.         'runserver':        scripts.RunserverCommand,  
  8.         'initdb':           scripts.InitDatabaseCommand,  
  9.         'reset':            scripts.ResetDatabase,  
  10.         'make_testdata':    scripts.MakeTestData,  
  11.         'compile_catalog':  scripts.CompileCatalogEx  
  12.     }  

コマンドの実装はこんな感じ
  1. from distutils.cmd import Command  
  2. class InitDatabaseCommand(Command):  
  3.     description = 'initializes the database'  
  4.     user_options = [  
  5.         ('drop-first''D',  
  6.          'drops existing tables first')  
  7.     ]  
  8.     boolean_options = ['drop-first']  
  9.   
  10.     def initialize_options(self):  
  11.         self.drop_first = False  
  12.   
  13.     def finalize_options(self):  
  14.         pass  
  15.   
  16.     def run(self):  
  17.         from solace import database  
  18.         if self.drop_first:  
  19.             database.drop_tables()  
  20.             print 'dropped existing tables'  
  21.         database.init()  
  22.         print 'created database tables'  

わかりやすい!

もう一つ勉強になったのがtests_require
  1. setup(  
  2.     name='Plurk_Solace',  
  3.     version='0.1',  
  4.     url='http://opensource.plurk.com/solace/',  
  5.     license='BSD',  
  6.     author='Plurk Inc.',  
  7.     author_email='opensource@plurk.com',  
  8.     description='Multilangual User Support Platform',  
  9.     long_description=__doc__,  
  10.     packages=['solace''solace.views''solace.i18n''solace.utils'],  
  11.     zip_safe=False,  
  12.     platforms='any',  
  13.     test_suite='solace.tests.suite',  
  14.     install_requires=[  
  15.         'Werkzeug>=0.5.1',  
  16.         'Jinja2',  
  17.         'Babel',  
  18.         'SQLAlchemy>=0.5',  
  19.         'creoleparser',  
  20.         'simplejson',  
  21.         'webdepcompress'  
  22.     ],  
  23.     tests_require=[  
  24.         'lxml',  
  25.         'html5lib'  
  26.     ], **extra  
  27. )  

ここで、テストのときだけ必要なライブラリを書くことができる。
python setup.py test
とすると依存ライブラリをとりにいきます。
すばらしい!


setuptoolsをもっと勉強します。
deploy+setup Scriptとして、今後活用していくことを誓います。