buchoを2to3でPython2/3両対応にするメモ¶
bucho-0.1.0 for Python2/3
最近Python3対応パッケージがぼちぼち出始めているみたいで、4/30に zc.buildout-2.0.0a もリリースされました。そこで、お試しにインストール出来そうなPython3向けパッケージを探してみたけどあんまり見つからなかったので、 bucho をPython3対応してみました。
pyreadline を2to3でPython2/3両対応にするメモ ではまった経験を元に、サクサク対応を進めていきます。
Python3対応メモ¶
例によって、とりあえず2to3変換してみます。 python32 c:\Develop\Python32\Tools\Scripts\2to3.py bucho
という感じでオプション無しで実行して、まずは変換差分を確認。
--- bucho/__init__.py (original)
+++ bucho/__init__.py (refactored)
@@ -1,6 +1,6 @@
# encoding: utf-8
import json
-import urllib
+import urllib.request, urllib.parse, urllib.error
# these methods are exposed to Internet by wsgi.py
__all__ = ['show', 'latest_status', 'all_status', 'torumemo']
@@ -152,14 +152,14 @@
def latest_status():
"""Print latest bucho's tweet.
"""
- url = urllib.urlopen('http://twitter.com/statuses/user_timeline/torufurukawa.json')
+ url = urllib.request.urlopen('http://twitter.com/statuses/user_timeline/torufurukawa.json')
tof = json.loads(url.read())
return tof[0]['text']
def all_status():
"""Print all bucho's tweet.
"""
- url = urllib.urlopen('http://twitter.com/statuses/user_timeline/torufurukawa.json')
+ url = urllib.request.urlopen('http://twitter.com/statuses/user_timeline/torufurukawa.json')
tof = json.loads(url.read())
for t in tof:
return t['text']
--- bucho/command.py (original)
+++ bucho/command.py (refactored)
@@ -62,7 +62,7 @@
for name in ['show', 'latest_status', 'all_status', 'torumemo']:
def makecmd(n):
def f(self, arg):
- print(getattr(bucho, n)())
+ print((getattr(bucho, n)()))
return 0
return f
setattr(BuchoCmd, "do_" + name, makecmd(name))
@@ -73,8 +73,8 @@
if options is None:
options = BuchoOptions()
c = cmdclass(options)
- except Exception, e:
- print e
+ except Exception as e:
+ print(e)
return
if options.args:
--- bucho/wsgi.py (original)
+++ bucho/wsgi.py (refactored)
@@ -1,4 +1,4 @@
-import urlparse
+import urllib.parse
import bucho
@@ -11,8 +11,8 @@
"""
def wsgi_app(environ, start_response):
- split_result = urlparse.urlsplit(environ['PATH_INFO'])
- paths = filter(None, split_result[2].split('/'))
+ split_result = urllib.parse.urlsplit(environ['PATH_INFO'])
+ paths = [_f for _f in split_result[2].split('/') if _f]
headers = [('Content-Type', 'text/plain')]
if not paths:
buchoはスレンダーなのであまり変換は多くありませんでしたが、実際に動かしてみると全然言うことを聞いてくれません。bucho, 見た目ほど単純ではないっぽい。
といことで、以下、はまったところをメモ。
printがbytesを受け付けない¶
Python2のUnicode文字列で、実行環境のコンソールに出力出来ない文字を含んでいる場合、 print value
で出力出来ない場合があるので、以下のようにごまかすことがあります(良い代案募集):
print value.encode(sys.stdout.encoding, 'replace')
しかしPython3のprint()にbytesを渡すと以下のようにreprした結果が出力されてしまいます。
Python2 の場合:
>>> print(b'bucho')
bucho
>>> print(u'部長'.encode(sys.stdout.encoding))
部長
Python3 の場合:
>>> print(b'bucho')
b'bucho'
>>> print('部長'.encode(sys.stdout.encoding))
b'\xe9\x83\xa8\xe9\x95\xb7'
とはいえ、Unicodeオブジェクトのままprintに渡してしまうと環境依存で出力出来ない文字に遭遇したときにUnicodeEncodeErrorになってしまうので、以下のようにして回避しました(ほんと、良い代案募集):
bucho_encoding = sys.stdout.encoding
if not bucho_encoding:
bucho_encoding = 'utf-8'
value = value.encode(bucho_encoding, 'replace')
value = value.decode(bucho_encoding, 'replace')
print(value)
なお、 エキスパートPythonプログラミング の日本語版で追加されたUnicode章(Appendix A)でこのあたりについて詳しく触れています (sys.stdout.buffer.writeでbytesを書き出せる (407ページ)、sys.stdout.encodingはファイルにリダイレクトしたときにNoneになるのでlocale.getpreferredencoding()を代わりに使う (399ページ)、など)。
wsgirefのappサンプルが動かない¶
Python-3.2のWSGIアプリケーションのサンプルコード のアプリ部分を抜き出すと以下のように書かれていましたが、残念ながらこのままでは動きません。
def simple_app(environ, start_response):
setup_testing_defaults(environ)
status = b'200 OK'
headers = [(b'Content-type', b'text/plain; charset=utf-8')]
start_response(status, headers)
ret = [("%s: %s\n" % (key, value)).encode("utf-8")
for key, value in environ.items()]
return ret
status は str 型でなければいけません。headersに設定するkey/valueもstr型でなければいけません。returnする値はbytesのリストなので、これは上記の記述で問題ありませんが、別のサンプルでは return b"Hello World"
って書いてあってこれは return [b"Hello World"]
じゃないとNGでした。
...というような事をつぶやいたらところ、 @methane からコメントを頂きました:
それは、この間PEP3333とかで結論が出た話で、ドキュメントの更新が間に合って ないみたいですね。 statusとheaderは両方共str型で、latin-1でエンコードして出力されるはずです。
—@methane http://twitter.com/methane/status/64516507572510720
なるほどー。とりあえず似たような報告は上がってなかったので http://bugs.python.org/issue11968 に超適当な報告をあげておきました。
json.loads()はbytesを受け付けない¶
以下のコードは Python2 で動作します:
url = urllib.urlopen('http://twitter.com/statuses/user_timeline/torufurukawa.json')
tof = json.loads(url.read())
しかし、これを単純に2ty3しただけの以下のコードは Python3 で動作しません:
url = urllib.request.urlopen('http://twitter.com/statuses/user_timeline/torufurukawa.json')
tof = json.loads(url.read())
原因は、json.loads()はstrを期待しているのに、url.read()の返値がbytesだったためです。ということで、Python3で動作させるために以下のように書き換えました:
url = urllib.request.urlopen('http://twitter.com/statuses/user_timeline/torufurukawa.json')
tof = json.loads(url.read().decode('ascii'))
さすがにasciiは手を抜きすぎか。 url.headers['content-type']
のcharsetを見てdecodeしないとだめか。めんどくさいな。
2to3対応setup.py¶
最後に、Python2/3両対応にするためにsetup.pyに手を加えます。
import setuptools
extra = {}
if sys.version_info >= (3, 0):
if not getattr(setuptools, '_distribute', False):
raise RuntimeError(
'You must installed `distribute` to setup bucho with Python3')
extra.update(
use_2to3=True
)
setuptools.setup(
name = 'bucho',
....
**extra
)
とりあえず今日のまとめ¶
buchoは手強い
bucho-0.1.0 リリース (ロゴがPython3だ!)