エキスパートPythonプログラミング (和訳)
Pythonプロフェッショナルプログラミング (2章分)
sphinx-intlの行数:
sphinx-intlの構成:
/
├─ README.rst
├─ setup.cfg
├─ setup.py
├─ tox.ini
├─ sphinx_intl/
│ ├─ __init__.py (3L)
│ ├─ __main__.py (5L)
│ └─ commands.py (575L)
└─ tests (497L)
sphinx-intlが使っている範囲で紹介
[speech]
ライブラリや関数の違いを吸収するのは簡単ですが、文法の違いを吸収するのは手間がかかります。どこが違って、どうやって吸収するのかについて、sphinx-intlが使用している範囲で紹介します。
バージョン判別フラグを用意して、以降のコードの書き分けに利用。
>>> PY2 = sys.version_info < (3, 0)
>>> PY3 = not PY2
>>> PY2
True
>>> PY3
False
他のバージョン向けにPyPIで提供している:
$ pip install argparse
他のバージョン向けにPyPIで提供している:
$ pip install ordereddict
Pythonバージョン別で使い分ける:
if sys.version_info < (2, 7):
from ordereddict import OrderedDict
else:
from collections import OrderedDict
if PY2:
def b(s):
return s
def u(s):
return unicode(s, "unicode_escape")
else:
def b(s):
return s.encode("latin-1")
def u(s):
return s
関数オブジェクトの属性。
def spam(name, age, kind=None):
pass
関数の引数の数や変数名とか色々取れる。
if PY2:
argcount = spam.func_code.co_argcount
varnames = spam.func_code.co_varnames[:argcount]
else:
argcount = spam.__code__.co_argcount
varnames = spam.__code__.co_varnames[:argcount]
try:
callable = callable
except NameError:
def callable(obj):
return any(
"__call__" in klass.__dict__
for klass in type(obj).__mro__
)
try:
execfile = execfile
except NameError:
def execfile(filepath, _globals):
f = open(filepath, 'rt')
source = f.read()
code = compile(source, filepath, 'exec')
exec(code, _globals)
execもPy3で文から式に変わりました。
from __future__ import with_statement
with open('file.txt', 'r') as f:
print f.read()
Python2のprint文の例
>>> print 'spam', 'egg', 'ham'
spam egg ham
Python2で括弧を付けるとタプルをprintしてしまう↓
>>> print('spam', 'egg', 'ham')
('spam', 'egg', 'ham')
Python3では普通にプリントされる
>>> print('spam', 'egg', 'ham')
spam egg ham
Python2系でのprint文の例:
print >>sys.stderr, 'image:', filename, 'loading...',
data = load_image(filename)
print('done.')
Python3系のprint関数だと:
print('image:', filename, 'loading...', end=' ', file=sys.stderr)
data = load_image(filename)
print('done.')
printを文ではなく式として解釈させる(2.5は非対応)
from __future__ import print_function
print関数は仕様が多いので、互換機能実装はとても面倒
def print_(*args, **kwargs):
fp = kwargs.pop("file", sys.stdout)
if fp is None:
return
def write(data):
if not isinstance(data, basestring):
data = str(data)
fp.write(data)
want_unicode = False
sep = kwargs.pop("sep", None)
if sep is not None:
if isinstance(sep, unicode):
want_unicode = True
elif not isinstance(sep, str):
raise TypeError("sep must be None or a string")
end = kwargs.pop("end", None)
if end is not None:
if isinstance(end, unicode):
want_unicode = True
elif not isinstance(end, str):
raise TypeError("end must be None or a string")
if kwargs:
raise TypeError("invalid keyword arguments to print()")
if not want_unicode:
for arg in args:
if isinstance(arg, unicode):
want_unicode = True
break
if want_unicode:
newline = unicode("\n")
space = unicode(" ")
else:
newline = "\n"
space = " "
if sep is None:
sep = space
if end is None:
end = newline
for i, arg in enumerate(args):
if i:
write(sep)
write(arg)
write(end)
Python2と3両対応コード書くのって、大変
[speech]
2to3を使ってコード変換する方法と、sixを使って共通コードで動作させる方法があります。一長一短ありますが、どのようなときにどちらを使うべきかなど紹介します。
Cons
2to3を使わず、両方で解釈できる方法で書く。
自力で、がんばる……
def print_(*args, **kwargs):
fp = kwargs.pop("file", sys.stdout)
if fp is None:
return
def write(data):
if not isinstance(data, basestring):
data = str(data)
fp.write(data)
want_unicode = False
sep = kwargs.pop("sep", None)
if sep is not None:
if isinstance(sep, unicode):
want_unicode = True
elif not isinstance(sep, str):
raise TypeError("sep must be None or a string")
end = kwargs.pop("end", None)
if end is not None:
if isinstance(end, unicode):
want_unicode = True
elif not isinstance(end, str):
raise TypeError("end must be None or a string")
if kwargs:
raise TypeError("invalid keyword arguments to print()")
if not want_unicode:
for arg in args:
if isinstance(arg, unicode):
want_unicode = True
break
if want_unicode:
newline = unicode("\n")
space = unicode(" ")
else:
newline = "\n"
space = " "
if sep is None:
sep = space
if end is None:
end = newline
for i, arg in enumerate(args):
if i:
write(sep)
write(arg)
write(end)
six (1.4.1 release 2013/9/2)
こういうこともあるんだね
[speech]
2013/7/1現在、Pythonのパッケージングは混乱しています。とりあえず今どうすると安定したパッケージ供給が出来るのか紹介します。
ここまでがPyCon JP 2011の頃。
これがPyCon JP 2012の前後。
PyCon APAC 2013の頃
setuptoolsを使おう! (distlibの世界になるまでは)
詳しくは PyCon APAC 2013 DAY1, パッケージングの今と未来 の発表を参照
requires = ['six', 'polib', 'sphinx']
if sys.version_info < (2, 7):
requires.append('ordereddict')
extras = {}
if sys.version_info < (2, 6):
extras['transifex'] = ['transifex_client==0.8']
else:
extras['transifex'] = ['transifex_client']
setup(
...
classifiers=[
"Development Status :: 4 - Beta",
"Environment :: Other Environment",
"License :: OSI Approved :: BSD License",
"Topic :: Documentation",
"Topic :: Software Development :: Documentation",
"Topic :: Text Processing :: General",
"Topic :: Utilities",
"Programming Language :: Python",
"Programming Language :: Python :: 2",
"Programming Language :: Python :: 2.5",
"Programming Language :: Python :: 2.6",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.1",
"Programming Language :: Python :: 3.2",
"Programming Language :: Python :: 3.3",
],
...
)
PyPIでこう表示される: sphinx-intl
Python2.5はそろそろ消滅すべき
2to3はデバッグ大変
six 便利
「Sphinxをはじめよう」売れ行き好調