Python3 プログラミング勉強会の補足

先日、Python3 プログラミング勉強会 を開催しました。

以下に資料の中で引用したサンプルコードを置いておきました。

ちなみにこういったサンプルコードをアップする前に pep8 と flakes と doctest のテストを実行しておけば typo してツッコミを受けることもありません。例えば pytest 使えば、以下のように簡単にできます。

$ py.test --pep8 --flakes --doctest-module --clear . 
============================= test session starts ==============================
platform linux -- Python 3.4.0 -- py-1.4.20 -- pytest-2.5.2
plugins: flakes, pep8, cache
collected 39 items 

algorithm_set1.py ....
generator_sample1.py ...
generator_sample2.py ......
generator_sample3.py .......
lru_cache_sample1.py ....
singledispatch_sample1.py ....
super_sample1.py ....
test.txt .
with_sample1.py ......
========================== 39 passed in 0.15 seconds ===========================

ページ14: バージョニングのお話

勉強会ではスライドを飛ばしたけど、バージョニングのスキームがすごいことになってます。

[N:]N(.N)*[{a|b|c|rc}N][.postN][.devN]

RPM 以外で Epoch をバージョニングのスキームに組み込んでいるのを私は初めてみました。PEP440 の提案者が Nick Coghlan (@) で、プロフィールをみると Red Hat で働いている開発者だったので元ネタは RPM なのかもしれません。あと、Python の3番目のバージョンをマイクロと呼ぶのも知りませんでした。

ページ16: setuptools の原罪と贖罪

このスライド1枚作るのに何時間もかけて情が移ってしまったために、このスライドを力説し過ぎて時間を浪費してしまいました。この資料の中で一番価値のあるスライドだと思っているいるので、何かに困ったときに見返してください。

ページ17: パッケージングと pbr

あー、pbr の話ももっとしたかったです。これだけで勉強会しても良いぐらいです。OpenStack プロジェクトで何が起きているか、みたいなのは今後の Python の未来に大きな影響を与えるような気がします。

簡単に紹介すると、setup.py にパッケージ情報を書かずに setup.cfg に Metadata2.0 のフォーマットでパッケージ情報を記述できます。

$ git clone https://github.com/openstack-dev/pbr
$ cd pbr/
$ vi setup.py
import setuptools
from pbr import util

setuptools.setup(
    **util.cfg_to_args())
$ vi setup.cfg 
[metadata]
name = pbr
author = OpenStack
author-email = openstack-dev@lists.openstack.org
summary = Python Build Reasonableness
...

git の履歴から AUTHORS や ChangeLog も自動生成してくれます。git のコミットログから ChangeLog を作ってくれるのは結構便利かもしれませんね。コミットログの最初の行を ChangeLog のサマリとして扱ってるようにみえます。マージのコミットログは無視してたりして賢いです。

$ workon pbr3  # pbr や関連ツールをインストールしておいた仮想環境を使う
(pbr3)$ python setup.py sdist
(pbr3)$ head AUTHORS 
Alex Gaynor <alex.gaynor@gmail.com>
Andrew Bogott <abogott@wikimedia.org>
Angus Salkeld <asalkeld@redhat.com>
Anthony Young <sleepsonthefloor@gmail.com>
Attila Fazekas <afazekas@redhat.com>
Ben Nemec <bnemec@redhat.com>
Bhuvan Arumugam <bhuvan@apache.org>
Brian Waldon <bcwaldon@gmail.com>
Chang Bo Guo <guochbo@cn.ibm.com>
ChangBo Guo(gcb) <eric.guo@easystack.cn>
...

(pbr3)$ cat ChangeLog 
CHANGES                                                                        
=======                                                                        
                                                                               
* Allow examining parsing exceptions                                           
* Update integration script for Apache 2.4                                     
* Restore Monkeypatched Distribution Instance                                  
* Register testr as a distutil entry point                                     
* Check for git before querying it for a version                               
* Allow _run_cmd to run commands in any directory                              
* Make setUp fail if sdist fails                                               
* Permit pre-release versions with git metadata                                
* Un-nest some sections of code                                                
                                                                               
0.8.2                                                                          
-----                                                                          
                                                                               
* Remove --use-mailmap as it's not needed                                      
* Fix typos in docs                                                            
                                                                               
0.8.1                                                                          
-----                                                                          
                                                                               
* pbr/testr_command.py: Add logging                                            
* Documentation updates                                                        
* Fixed a typo in the documentation                                            
* Make source configurable when using --coverage                               
* README.rst: tweaks                                                           
* Format autoindex.rst file properly                                           
* make pbr use hacking directly   
...

(pbr3)$ git log
commit e1b98f578a0f94a791210dd48530e2fed43fbe61
Merge: fa17f42 6541911
Author: Jenkins <jenkins@review.openstack.org>
Date:   Mon Jun 30 17:34:07 2014 +0000

    Merge "Check for git before querying it for a version"

commit fa17f42d7d195dab77c042ac4881ac63bfb638f3
Author: Robert Collins <rbtcollins@hp.com>
Date:   Mon Mar 17 09:46:58 2014 +1300

    Allow examining parsing exceptions.
    
    Having to put breakpoints in pbr to diagnose issues is bad for dealing
    with reports from users.
    
    Change-Id: Ifecf4c4e4bb5955e0e5feb4bf5b5b85150b08ebe

commit d815366577a7dbb7a8dab66ab105e2803af4c007
Merge: ec1009c bdb0191
Author: Jenkins <jenkins@review.openstack.org>
Date:   Fri Jun 27 02:29:12 2014 +0000

    Merge "Un-nest some sections of code"

commit ec1009cf197ed555b29b6f226adb300914ea5bd0
Author: Sean Dague <sean@dague.net>
Date:   Thu Jun 26 08:22:47 2014 -0400

    Update integration script for Apache 2.4
    
    Apache 2.4 requires site configuration files to have a ".conf"
    extension, and Apache 2.2 does not want the extension. Add logic to
    figure out the right name for the file so we can run the tests against
    both versions of Apache.
    
    Fixes bug: #1334326
...

テストランナーに tox と testr を使います。setuptools の拡張コマンドに testr オプションが追加されています。デフォルトでテストを並列実行 (CPU の数かな?) してくれるようです。

(pbr3)$ python setup.py testr
running testr
running=OS_STDOUT_CAPTURE=1 OS_STDERR_CAPTURE=1 OS_TEST_TIMEOUT=60 ${PYTHON:-python} -m subunit.run discover -t ./ . --list 
running=OS_STDOUT_CAPTURE=1 OS_STDERR_CAPTURE=1 OS_TEST_TIMEOUT=60 ${PYTHON:-python} -m subunit.run discover -t ./ .  --load-list /tmp/tmpe9wwqlo3
running=OS_STDOUT_CAPTURE=1 OS_STDERR_CAPTURE=1 OS_TEST_TIMEOUT=60 ${PYTHON:-python} -m subunit.run discover -t ./ .  --load-list /tmp/tmpbwa1_yy8
running=OS_STDOUT_CAPTURE=1 OS_STDERR_CAPTURE=1 OS_TEST_TIMEOUT=60 ${PYTHON:-python} -m subunit.run discover -t ./ .  --load-list /tmp/tmpqv7w0s4v
running=OS_STDOUT_CAPTURE=1 OS_STDERR_CAPTURE=1 OS_TEST_TIMEOUT=60 ${PYTHON:-python} -m subunit.run discover -t ./ .  --load-list /tmp/tmpqdbg34vv
running=OS_STDOUT_CAPTURE=1 OS_STDERR_CAPTURE=1 OS_TEST_TIMEOUT=60 ${PYTHON:-python} -m subunit.run discover -t ./ .  --load-list /tmp/tmpkd169eki
running=OS_STDOUT_CAPTURE=1 OS_STDERR_CAPTURE=1 OS_TEST_TIMEOUT=60 ${PYTHON:-python} -m subunit.run discover -t ./ .  --load-list /tmp/tmpch2tkra0
running=OS_STDOUT_CAPTURE=1 OS_STDERR_CAPTURE=1 OS_TEST_TIMEOUT=60 ${PYTHON:-python} -m subunit.run discover -t ./ .  --load-list /tmp/tmp6h4ef3mr
running=OS_STDOUT_CAPTURE=1 OS_STDERR_CAPTURE=1 OS_TEST_TIMEOUT=60 ${PYTHON:-python} -m subunit.run discover -t ./ .  --load-list /tmp/tmp6407izna
Ran 57 tests in 2.529s (-0.726s)
PASSED (id=9, skips=8)

(pbr3)$ python setup.py testr --testr-args --concurrency=1
running testr
running=OS_STDOUT_CAPTURE=1 OS_STDERR_CAPTURE=1 OS_TEST_TIMEOUT=60 ${PYTHON:-python} -m subunit.run discover -t ./ .  
Ran 57 tests in 9.140s (-0.065s)
PASSED (id=7, skips=8)

ページ21: Pythonic なコード

エキスパート Python プログラミングから引用しました。

Pythonic とは、小さなコードパターンに最も効率的なイディオムを使ったコーディングや構文

最近、他人の Python コードを読んでいて「何かおかしい」と違和感を感じるときは、この Pythonic の概念を大事にしてコードを書いているかどうかの違いによるもののような気がしてきました。OSSフレームワークやライブラリを使うときにも、その開発者の意図に共感できると親しみがもてたりするものです。

Pythonista というと、一貫性にこだわる怖い人たちなんでしょう?のイメージがあります。もちろんそういった場面にもよく遭遇しますが、

Although practicality beats purity. ( とはいえ、純粋さよりも実用的であること )

  • PEP8 の一節

A Foolish Consistency is the Hobgoblin of Little Minds

とか言っていて、一貫性にこだわり過ぎるのもよくないという戒めもあります。こだわりは強くても極論はよくないといった感じですかね。

ページ23: functools.singledispatch

汎用関数が気持ちよく書ける、デコレーターの応用例の紹介をしました。
勉強会でも singledispatch をコードリーディングしたのですが、ソースもシンプルなのでデコレーターの実用例としておもしろいと思います。

ページ33: Python 2.4 との互換性を気にする

これを気にしなくなって Python3 に感激したのも事実の1つです。

標準モジュールがなかったり、機能のバージョン毎の差異を吸収するように実装したものがいくつかありました (ここにサンプルコードを置きました) 。2.4 対応を考慮しながらコードを書くと、常にこういったことに悩まされながらコードを書いているという例です。あくまで自分の用途にあわせた実装なのでサンプルコードは参考程度にみてください。

  • json の読み込み
  • itemgetter の複数引数の対応
  • datetime.strptime の互換関数

ページ34: 集合型 set の応用例

これもスライドを飛ばしたけど、集合型をこんな用途にも使えるんだなぁと感心したスニペットです。アルゴリズムを勉強したくなる気を起こさせるコードでした。

def has_invalid_fields_loop(fields):                                             
    """                                                                          
    >>> has_invalid_fields_loop(['foo', 'bar', 'foo', 'test'])                      
    True                                                                            
    >>> has_invalid_fields_loop(['foo', 'bar'])                                     
    False                                                                           
    """                                                                             
    for field in fields:                                                            
        if field not in ['foo', 'bar']:                                             
            return True                                                             
    return False                                                                    
                                                                                    
                                                                                    
def has_invalid_fields_set(fields):                                                 
    """                                                                             
    >>> has_invalid_fields_set(['foo', 'bar', 'foo', 'test'])                       
    True                                                                            
    >>> has_invalid_fields_set(['foo', 'bar'])                                      
    False                                                                           
    """                                                                             
    return bool(set(fields) - set(['foo', 'bar']))

ページ35: super はスーパーな話

簡単な分かりやすいサンプルコードを提示したつもりだったのに、絶賛大ブーイングを受けたサンプルコードがこれでした。こんな複雑なことをする意図が分からない的な。

メソッド解決順序(MRO) がとても分かりやすいけれど、全てのクラスが object を継承するようになったため、どんな多重継承を行っても必ずダイヤモンド継承になる。多重継承が本当に必要かどうかの議論はもちろんあって良いですが、mixin 的に機能を扱いたいときとか、普通にその MRO や移譲先を意識して設計するでしょうし、サブクラスで拡張することを意識してコア機能作ったりするんじゃないかなぁとも思います。フレームワークプラグインのような、それぞれの開発者が異なるケースでもたくさん応用例がありそうに思いますが、ちゃんと調べないと分からないぐらいにはややこしいです。

後半で時間がなかったからちゃんと背景を説明をしなかったせいでもあるけれど、もっと私も勉強しないといけないです。

ページ42: The Hacker's Guide to Python

Python WeeklyThe Hacker's Guide to Python が紹介されていて、機会があったら読もうと思っていました。ちょうど勉強会するからと読み始めたらおもしろくて、この本を読んだために勉強会の内容もかなり変わってしまったぐらい影響を受けました。本当は AST やパフォーマンスの章も紹介したかったけれど、準備が全然間に合わなくて断念しました。

まとめ

当初は社内の Python ツールリファクタリング的な話をしながら Python3 楽しいよという話をしようと構想していました。いろいろ調べたり、The Hacker's Guide to Python を読んだりしているうちにどんどん変わっちゃってこんな感じになってしまいました。せっかく資料をまとめたので何かの役に立ったら嬉しいです。

Ubuntu 14.04 で vim パッケージをリビルドする

Ubuntu 14.04 で vim をインストールすると python3 インターフェースが無効になっています。python3 インターフェースを有効にするために deb パッケージをリビルドしてみます。vim拡張機能をチェックするには以下のようにバージョンを表示します。

$ vim --version  # vim.gtk を使ってます
VIM - Vi IMproved 7.4 (2013 Aug 10, compiled Jan  2 2014 19:39:59)
適用済パッチ: 1-52
Modified by pkg-vim-maintainers@lists.alioth.debian.org
Compiled by buildd@
Huge 版 with GTK2 GUI.  機能の一覧 有効(+)/無効(-)
+acl             +farsi           +mouse_netterm   +syntax
+arabic          +file_in_path    +mouse_sgr       +tag_binary
+autocmd         +find_in_path    -mouse_sysmouse  +tag_old_static
+balloon_eval    +float           +mouse_urxvt     -tag_any_white
+browse          +folding         +mouse_xterm     +tcl
++builtin_terms  -footer          +multi_byte      +terminfo
+byte_offset     +fork()          +multi_lang      +termresponse
+cindent         +gettext         -mzscheme        +textobjects
+clientserver    -hangul_input    +netbeans_intg   +title
+clipboard       +iconv           +path_extra      +toolbar
+cmdline_compl   +insert_expand   +perl            +user_commands
+cmdline_hist    +jumplist        +persistent_undo +vertsplit
+cmdline_info    +keymap          +postscript      +virtualedit
+comments        +langmap         +printer         +visual
+conceal         +libcall         +profile         +visualextra
+cryptv          +linebreak       +python          +viminfo
+cscope          +lispindent      -python3         +vreplace
...

自分で vim のソースをダウンロードしてきてビルドする方法もありますが、なるべくディストリビューションのもの (と同じ設定) を使いたいと思う人もいるでしょう。ここでは deb パッケージをリビルドすることで python3 インターフェースを有効にしてみます。

前準備

まずビルドに必要な依存パッケージをまとめてインストールします。私の環境では以下のパッケージを追加するとビルドできましたが、これはそれぞれの環境によって違うかもしれません。

$ sudo apt-get build-dep vim  # vim のビルドに必要な依存パッケージをインストール
$ sudo apt-get install python3-dev ruby-dev tcl8.5-dev  # vim の拡張機能のビルドに必要な依存パッケージをインストール
$ sudo apt-get install devscripts  # deb パッケージのビルドツールをインストール

次にソースをダウンロードします。

$ mkdir /tmp/vim-build
$ cd /tmp/vim-build/
$ apt-get source vim
$ ls
vim-7.4.052  vim_7.4.052-1ubuntu3.debian.tar.gz  vim_7.4.052-1ubuntu3.dsc  vim_7.4.052.orig.tar.gz
$ cd vim-7.4.052
  • vim の configure オプションを調べる
$ ./configure --help
ビルド

バージョン番号もここで指定したものに置き換えられるようです。ディストリビューション名やメールアドレスなど、適切な内容を changelog に書かないとビルド時にエラーになります。

$ debchange -i
vim (2:7.4.052-1ubuntu4) trusty; urgency=medium
                                                                               
  * enabled python3 interface.
                                                                               
 -- t2y <email>  Sat, 26 Apr 2014 18:43:45 +0900
  • rules ファイルの編集

python3 インターフェースを有効にするための configure オプションを追加します。また vim-nox のテスト中にエラーになったのでここではビルド後にテストを実行しないように編集しています。

$ vim debian/rules
--- orig-vim-7.4.052/debian/rules	2014-04-26 15:42:19.264743944 +0900
+++ vim-7.4.052/debian/rules	2014-04-26 16:42:47.818878711 +0900
@@ -27,11 +27,11 @@
 BUILDER := $(shell echo $${DEBEMAIL:-$${EMAIL:-$$(echo $$LOGNAME@$$(cat /etc/mailname 2> /dev/null))}})
 
 MAKETEST := no
-ifeq ($(DEB_HOST_GNU_TYPE),$(DEB_BUILD_GNU_TYPE))
-  ifeq (,$(filter nocheck,$(DEB_BUILD_OPTIONS)))
-    MAKETEST := yes
-  endif
-endif
+#ifeq ($(DEB_HOST_GNU_TYPE),$(DEB_BUILD_GNU_TYPE))
+#  ifeq (,$(filter nocheck,$(DEB_BUILD_OPTIONS)))
+#    MAKETEST := yes
+#  endif
+#endif
 
 ifneq (,$(filter parallel=%,$(DEB_BUILD_OPTIONS)))
     NUMJOBS = $(patsubst parallel=%,%,$(filter parallel=%,$(DEB_BUILD_OPTIONS)))
@@ -108,7 +108,7 @@
 ALLINTERPFLAGS+=--disable-mzschemeinterp
 ALLINTERPFLAGS+=--enable-perlinterp
 ALLINTERPFLAGS+=--enable-pythoninterp --with-python-config-dir=$(shell python-config --configdir)
-ALLINTERPFLAGS+=--disable-python3interp
+ALLINTERPFLAGS+=--enable-python3interp --with-python3-config-dir=$(shell python3-config --configdir)
 ALLINTERPFLAGS+=--enable-rubyinterp
 ALLINTERPFLAGS+=--enable-tclinterp
  • ビルド
$ debuild -us -uc
$ ls ../  # 親ディレクトリにパッケージが作成される

インストール

必要なパッケージをインストールします。私は vim-gtk を使っています。

$ cd ../
$ sudo dpkg -i vim_7.4.052-1ubuntu4_amd64.deb \
               vim-common_7.4.052-1ubuntu4_amd64.deb \
               vim-doc_7.4.052-1ubuntu4_all.deb \
               vim-runtime_7.4.052-1ubuntu4_all.deb \
               vim-tiny_7.4.052-1ubuntu4_amd64.deb \
               vim-gui-common_7.4.052-1ubuntu4_all.deb \
               vim-gtk_7.4.052-1ubuntu4_amd64.deb 

$ dpkg -l | grep vim
ii  vim               2:7.4.052-1ubuntu4     amd64     Vi IMproved - enhanced vi editor
ii  vim-common        2:7.4.052-1ubuntu4     amd64     Vi IMproved - Common files
ii  vim-doc           2:7.4.052-1ubuntu4     all       Vi IMproved - HTML documentation
ii  vim-gtk           2:7.4.052-1ubuntu4     amd64     Vi IMproved - enhanced vi editor - with GTK2 GUI
ii  vim-gui-common    2:7.4.052-1ubuntu4     all       Vi IMproved - Common GUI files
ii  vim-runtime       2:7.4.052-1ubuntu4     all       Vi IMproved - Runtime files
ii  vim-tiny          2:7.4.052-1ubuntu4     amd64     Vi IMproved - enhanced vi editor - compact version

オリジナルの vim --version の出力と比べると python3 インターフェースが有効になっているのが分かります。

$ diff -u orig-vim.gtk-version.txt <(vim --version)
--- orig-vim.gtk-version.txt	2014-04-26 15:48:29.234466537 +0900
+++ /dev/fd/63	2014-04-26 17:12:13.006045318 +0900
@@ -1,7 +1,7 @@
-VIM - Vi IMproved 7.4 (2013 Aug 10, compiled Jan  2 2014 19:39:59)
+VIM - Vi IMproved 7.4 (2013 Aug 10, compiled Apr 26 2014 16:45:41)
 適用済パッチ: 1-52
 Modified by pkg-vim-maintainers@lists.alioth.debian.org
-Compiled by buildd@
+Compiled by t2y@...
 Huge 版 with GTK2 GUI.  機能の一覧 有効(+)/無効(-)
 +acl             +farsi           +mouse_netterm   +syntax
 +arabic          +file_in_path    +mouse_sgr       +tag_binary
@@ -18,8 +18,8 @@
 +cmdline_info    +keymap          +postscript      +virtualedit
 +comments        +langmap         +printer         +visual
 +conceal         +libcall         +profile         +visualextra
-+cryptv          +linebreak       +python          +viminfo
-+cscope          +lispindent      -python3         +vreplace
++cryptv          +linebreak       +python/dyn      +viminfo
++cscope          +lispindent      +python3/dyn     +vreplace
 +cursorbind      +listcmds        +quickfix        +wildignore
 +cursorshape     +localmap        +reltime         +wildmenu
 +dialog_con_gui  +lua             +rightleft       +windows

python/dyn, python3/dyn に変わってしまいました。vim のドキュメントによると、Windows だと DLL を探すように書いてあります。Linux だと関係ないのかな?これでも Python 拡張が動いたので気にしないことにします。

$ vim
:help python-dynamic
----
9. Dynamic loading                                      python-dynamic         
                                                                               
On MS-Windows the Python library can be loaded dynamically.  The :version      
output then includes +python/dyn.                                              
                                                                               
This means that Vim will search for the Python DLL file only when needed.      
When you don't use the Python interface you don't need it, thus you can use    
Vim without this DLL file.

考える生き方

極東ブログ の著者の著書を読んだ。

それまでブログの存在は知っていたし、twitter 経由でリンクから記事を読んだことも何度かあった。著者がアルファブロガーの1人だということも知っていた。著者のブログである記事の内容を覚えている。いま見返すと、2年前の記事だった。2年前の記事を何となく読んだだけで未だに記憶に留めているというのは、よくよく考えてみるとすごいことだ。

引用が長くなるので結論だけ紹介するが、この結びが強く印象に残っている。

そういう無名の社会人が困難な状況のなかで「公」を支えてくれるおかげで、私たちは生きていけるわけです。みなさんの、誰かもそうなります。

 だから、いつも思うのです、ありがとう。

4月から新社会人になる皆さんへ: 極東ブログ

本書は何か

たぶん何でもないの只の自伝だと著者なら言うかもしれない。著者は自分の生き方や過去を「からっぽの人生」だったと振り返っているが、本書を読む限り、そんな自虐するようなものでもないように思えた。普通の人の、普通の人生も、端からみると全然普通じゃないというのを端的に表しているのかもしれない。

著者が「からっぽ」だというのは、世の中で理想とされるようなレールに則った人生ではなかったということを指しているのかもしれない。それは社会的に承認されることのない生き方だったのかもしれない。著者の年齢が55歳とあるので、いまよりもずっとそういったレールから外れた生き方に理解がなかったり、厳しかった時代だと思う。

著者のブログのファンならば興味深く読めると思う。私は逆だけど、本書を読んで著者の人となりを知って rss reader に追加した方だ。ブログでもそうだけど、読みやすく、ユーモアを交え、押し付けがましくない著者の文体に好感がもてる。著者は否定しても、アルファブロガーというのはやはりちょっと普通じゃないと思うので、普通の人が普通の生きるための参考になるかは正直分からない。でも、誰彼もその人の人生であって、それが普通か普通でないかは、さして意味はないというのを考えさせられる気もする。

テーマが多い

著者の半生を綴ったものだからテーマが多い。仕事、家族、恋愛、難病、学問、社会、文化、人生、、、色んなことが書いてある。その分、著者の主張のようなものがあるわけではない。ブログの延長と言えばそうかもしれないが、本書は誰かのための、何かのためのものという目的では書かれていない。読者が読んで何かを考えるきっかけになるようなものだと思う。

私の中では、最もおもしろかったのは、第5章の「勉強して考えたこと」でした。著者の勉強に対する考え方や向き合ってきた姿勢が読み取れて興味深かった。

勉強する意味

著者の勉強する意味とはこうらしい。

私は、楽しいからだと思う。もちろん、私がそういうタイプの人だからというのはある。
(中略)
知るということ、それ自体が楽しい。子どものころは、ほとんどの人がそうだった。それをいくつになっても維持していくようにするのが勉強ではないかと思う。

著者と自分の共通する点が1つある。それは職を転々と変えてきたところだ。一般的に職を変えることはあまり良いことではないだろう。ただ、考えるという点において環境を変えるというのは大きな影響力をもつように思う。

考える習慣を持たない人に共通する問題点として 「無知の知」 ならぬ 「無知の無知」 があります。 自分が分かってないことを分かってない、 自分の浅慮では到底及ばない領域があることが全く想像できない、 いわば 「井の中の蛙」 状態ですね。

仙石浩明の日記: 「無知の無知」への 3ステップ

また別のブログの引用だけど、またしても新社会人向けに書かれた記事だw、なんだ?そんなことも分かってないのか自分、、、という気分だ。

長く同じ環境にいると人間は慣れる。慣れると、考えずに行動できるようになる。それ自体が悪いわけではないけど、人間は楽をする生きものだから無意識にものごとを深く考えないようになってしまうことがあるように思う。そのときにこの「無知の無知」が起こる気がする。私の場合、転職を繰り返すうちに、前の職場で通用した知識やスキルが次の職場では全く通用しないことが起こる。そんなときに強く実感する。自分は何も分かってなかったんだなぁと。

私にとっての、勉強する意味は2つある。

一つは、勉強してないと、この先生き残れないんじゃないかという不安がある。周りの人たちも当たり前のように勉強しているのを見てきたのでそこに疑問はないし、勉強せずに活躍している人もやはり見たことがない。もう一つは、分からないと悔しい。気持ち悪い。恥ずかしい。そういった負の感情へのコンプレックスがある。

著者の知りたいという動機とは純粋度が違う。違う分、業務の勉強なんかには向くけど、自然科学とか、興味がないものの勉強には食指が動かない。

国際性の中のエピソード

著者の大学時代の、留学生とのやり取りのエピソードでおもしろいものがあった。

「NO」と言えるのは、人間が対等なときだ。人間が人間であるなら、違う考えをもって表明するときは、「NO」という。

気さくな友だちには NO と言えても、そうじゃない知り合いとか、同僚とか、そういう人たちに NO と言いにくかったりするのは、そういった背景もあるんだなぁと省みてしまった。ここで言う対等というのは立場的なものじゃなくて人間関係なようにも思う。

リベラル・アーツという概念

この節がすごくおもしろかった。この節を読むだけで純粋に勉強する楽しみというのはこういうことなんだなと分かった気になる。

メカニカル・アーツの対立語としての wikipedia:リベラル・アーツ について述べている。この wikipedia の説明もおそらくは正しいのだろうけど、本書と比べて、その時代の背景や文化的な側面があまり書かれていない。辞書を引けばその定義が書いてある。そういうものだけど、何だかしっくりこない。

本書では、まず「リベラル」とは自由市民を指していて、自由市民とは、奴隷とは、枝芸とは、そういったあれを知るためにはこれを知る必要があって、背景が分かった上で最終的にはこうなんですと展開している。それはほんの上辺でしかないけど、体系的に学ぶというのはそういうことかとも思う。そして、それがおもしろいから勉強するんだなということも分かる。決して wikipedia を作るために勉強するわけではないんだ。

リベラル・アーツが何ものかが分かったところでその必要性を説く。きっと著者の講義を受けられるとしたらこんな感じで勉強する楽しさを教えてくれるように思う。

また余談だけど、art という単語から想像する日本語は「芸術」だろう。実際、その訳語が第一義だから正しいのだけど、辞書を引くと art という単語には「技術」や「技能」といった訳語も出てくる。ここで言う、リベラル・アーツの art というのは、徒弟訓練で学ぶ技能の体系のことを指すらしい。

"The Art of Computer Programming", "The Art of UNIX Programming" など、Art of と銘を打つコンピューター関係の書籍はちらほらある。それらのイメージを単純に私は「芸術」と捉えていたのだけど、なんだ技術体系の書籍だという見方もあるのかと再発見した次第だ。

終わりに

そして、やはり結びの言葉も良い。

こう言ってもいいのかもしれない。人生の敗北者であっても、貧しい生活でも、学ぶことで人生は豊かになる。

希望はある。

考える生き方

考える生き方

パーフェクトPython

Python3 に特化した専門書という位置付けですが、(Python3 に関した) 言語仕様やその変更、ライブラリの詳細の違いなどを除けば、Python2 でも活用できる知識が大半です。まだ Python2 しか使っていないという方でも十分に役立つ内容だと思います。

ただ、本書を読み進める上で1つだけ忘れてはいけないことがあります。Python 全般について丁寧に解説されていますが

著者名が Python サポーターズとなっていますが、ついったーなんかでは Python モヒカンズなんて言われています。

パーフェクト Python の執筆に参加しました — プログラマのネタ帳 二冊目

と何だかこわい人たちが書いた本だということです。まずはそのことを念頭におくことで本書を楽しんで読み進められる心の準備が整った、言い換えると、覚悟はできたと言えます。そうすれば、途中でこわくなってきても勇気をもって立ち向かえますからね。

気になったところや興味深かったところを付箋付けしていったらこんなことになってしまいました。こわいことがたくさんです。

目次は以下の通りです。

1章 Pythonの概要

  • Part2 言語仕様

2章 Pythonの基本
3章 型とリテラル
4章 制御構文
5章 関数
6章 クラス
7章 モジュールとパッケージ
8章 拡張モジュールと組み込み
9章 標準ライブラリ

  • Part3 実践的な開発

10章 コマンドラインユーティリティ
11章 チャットアプリケーション
12章 アプリケーション/ライブラリの配布
13章 テスト
14章 Webプログラミング

  • Part4 適用範囲

15章 学術/分析系ライブラリ
16章 マルチメディア
17章 ネットワーク
18章 データストア
19章 運用/監視

Appendix
A 環境構築
B 標準ライブラリ

章ごとにこわくなったところをみていきます。

追記:
内輪的なものが気になるという意見も頂きました。
悪い意図はなかったので気になる方は以下のように置き換えて読んでください。

こわい = (個人的に) 興味がある、気になる、分からなかった、おもしろかった、不思議に思った

1章 Pythonの概要

Python の禅

Python の禅を全て紹介していた書籍は、私が読んだ中ではなかったように思います。こういった思想的なものは、最初のうちは言葉通りでしか理解できませんが、Python でプログラムを書くのに慣れていったり、開発コミュニティでやり取りしていくうちに、議論の根幹の部分の、共感できる要素になっていったりする気もします。

最初に思想的なところから啓蒙してくる辺り、Python サポーターズに洗脳されるんじゃないかと警戒したくなります。まだ初めの方なのでこわがらずに Python サポーターズの価値観に共感して読み進めましょう。

  • Beautiful is better than ugly.

最後の方に PerlRuby正規表現Python との違いをさらっと紹介しています。Python は言語の機能として正規表現をサポートせず、re という標準モジュールにより提供していることの背景を期待して読みましたが、、、書かれていませんでした。

なまじ説明がないのは「分かれよ」というメッセージです。Python正規表現リテラルをサポートしないのは、記号の多用を避けることを意図したものだったような気がしますが、情報ソースがどこだったのか見つけられませんでした。私の解釈があっているのか、ちょっとこわいです。

  • Flat is better than nested.

Java のパッケージの名前空間は長くて冗長だというものに対する否定やユーモアからの禅です。厳密に言うと、Java のパッケージは、ファイルシステムの階層構造と一致させる必要はありませんが、慣習的に (分かりやすさのために) そうなっているようです。Python のパッケージは __init__.py を置く決まりになっているのでファイルシステムと一致させる必要があります。データ構造やプログラミングの階層構造のことだと思わせておいて Java を dis ってるのがこわいです。

column: 名前の由来

本書では、spam や egg という変数名を使ったサンプルコードがたくさん紹介されていて、モンティ・パイソンから引用したものだとコラムにあります。

プログラムの挙動を説明するときなど、短いスニペットに使うときもありますが、意味のない名前を付けるのはなるべく避ける方が良いです。というのは、プログラミングの重要な要素の1つに「名前を付ける」ということがあります。ここで言うのは以下のような意味です。

  • 適切な名前を付ける
  • 分かりやすい名前を付ける
  • 簡潔な名前を付ける

この名前付けというのは、簡単なようでプログラムの規模が大きくになるにつれ難しくなります。変数名に限らず、小さい関数に分割しても関数名で悩み、クラス名で悩み、モジュール名で悩みます。その処理そのもの、責務、意図といった多くの概念を簡潔に表そうと考えるほど、名前を付けるのは難しいものです。

spam や egg というのは、ただの名前の過ぎないので、プログラマーにとって重要な「名前を付ける」という素養を軽視するのは、あまり良くない習慣だと言えます。こんなことを書いて大丈夫か、自分がこわいです。

IPython のオートコンプリート機能

IPython の機能の1つとしてコード補完が紹介されています。

私も普段は IPython を使っていますが、サーバーでのデバッグなど、IPython がインストールされていない環境でコード補完を行いたいときもあるでしょう。readline モジュールがインストールされている必要がある点は同じですが、IPython をインストールしなくても Python の標準ライブラリでコード補完できます。

>>> import readline, rlcompleter
>>> readline.parse_and_bind('tab:complete')

覚えておくと、ちょっとしたときに便利です。

たぶん Python サポーターズは、コード補完なんて軟弱な機能を使っていません。こわいです。

3章 型とリテラル

ucs2 と ucs4

PyCon JP 2012 で Dan Kogai 氏が指摘されていたのが印象に残っています。

Python 3.2 まではビルドオプションで ucs2 と ucs4 のどちらかを指定していました。この違いにより、16bit で表せるコードポイントを超えた部分に定義された文字列長が違ってしまうという問題がありました。Python 3.3 (PEP 393) からその問題が解消され、正しい文字列長が返されるようです。Python後方互換性を重視するため、マイナーバージョンアップにより以前書いたコードが動かなくなることはありませんが、あるバージョンから内部の仕組みが変わることはあります。

多くの人はディストリビューションが提供するバイナリを使うため、どちらのビルドオプションか意識せずに使っているでしょう。いまが過渡期であることの、問題の1つですね。

Python サポーターズは自分でビルドしているそうです。こわいです。

sys.intern

私自身、こういった最適化を行いたい状況に遭遇したことはありませんが、http://d.hatena.ne.jp/atsuoishimoto/20110418/1303094478 で知り、そんな仕組みがあるんだと印象に残っている機能です。

>>> import sys
>>> 'spam'.lower() is 'spam'.lower()
False
>>> sys.intern('spam'.lower()) is sys.intern('spam'.lower())
True

これはこわくありません。

タプルの構文

要素が1つの場合には tuple である事が解らなくならないように、(1,) のように末尾にカンマを記述します。

これは少し誤解を招く表現です。というのは、分からなくならないようにこのような記述をするのではなく、tuple の構文はカンマで区切るものであり、カッコは人間が分かりやすいように付けているものだからです。

>>> type((1))
<class 'int'>
>>> type((1,))
<class 'tuple'>
>>> 1,
(1,)

要素1のリストはカンマが必要でないので混同しがちなところかもしれません。

>>> [1]
[1]
>>> type([1])
<class 'list'>

カンマを忘れて tuple のつもりが int だったりしたらこわいです。

追記: 補足もらった、ありがとう

4章 制御構文

column: 内包表記のメリット

シンプルなループは積極的に内包表記を使った方がパフォーマンス面でメリットがあります。

>>> r = [i for i in range(10)]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> type(r)
<class 'list'>

私は map の方がコーディング的には好みですが、Python3 からはリストが返されるのではなく map オブジェクトが返されるようになりました。

>>> r = map(lambda x: x, range(10))
>>> type(r)
<class 'map'>
>>> r[1]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'map' object is not subscriptable

map オブジェクトは、ジェネレーターとしては判別されないみたいですね。

>>> import types
>>> isinstance(r, types.GeneratorType)
False

結果をリストで扱いたいときは組み込み関数の list で変換できますが、そういった用途なら内包表記を使う方が簡潔で良いですね。

>>> r = list(map(lambda x: x, range(10)))
>>> r[1]

従来からのリスト内包表記に加えて、セット内包表記、辞書内包表記も使えるようになりました。

>>> {x for x in [1, 2, 3, 2, 1, 5]}
{1, 2, 3, 5}
>>> {key: value for key, value in zip(['a', 'b'], [1, 2])}
{'b': 2, 'a': 1}

(慣れの問題で) ちょっと違和感も感じますが、コーディング的におもしろいです。こわくはありません。

例外オブジェクトの特殊メソッド

前後に __ (私はアンダーアンダーと呼んでます) で囲まれたメソッドを、Python では特殊メソッドと呼びます。ある人は、特殊メソッドは「Python の黒魔術だ」と表現していましたが、どんな特殊メソッドがあるのか知っておくというのは Python のプログラミングで重要なことです。

例外オブジェクトで3つの特殊メソッドが紹介されていました。

  • __traceback__: 例外発生時のトレースバックが格納される
  • __context__: 例外発生中に例外が発生したとき、元の例外オブジェクトが格納される
  • __cause__: "raise Error from err" としたとき、元の例外オブジェクトが格納される

この手のものは、時間とともにどんどん増えていきそうな雰囲気を感じます。便利なのは便利なのでしょうが、バージョンにより使える特殊メソッドがあるというのは意識しておく必要がありそうです。

Python サポーターズは、特殊メソッド を全て暗記しているそうです。こわいです。

5章 関数

サブジェネレーターへの委譲

Python 3.3 の目玉機能の1つです。

def generator():
    yield from sub_generator()

この処理をちゃんと実装しようとすると、かなり煩雑になるというのが PEP 380 Formal Semantics にあります。PEP 380 は粗訳したまま放ったらかしです、こわいというかごめんなさい。

6章 クラス

super とメソッドの検索順序 (mro)

Python3 から super を呼び出す引数を省略できるようになったのが簡潔で良いですね。

super と mro は、Python で委譲を実装する仕組みの1つです。super のメリットが分かる簡単なサンプルを紹介します (Pythonのsuper()ができること から)

  • 継承元の基底クラスを変更するときにコードを書き換える必要がない
  • サブクラス側でスーパークラスのデフォルトの委譲先を変更できる
>>> class MyDict(dict): pass
... 
>>> MyDict.__mro__
(<class '__main__.MyDict'>, <class 'dict'>, <class 'object'>)
>>> class YourDict(dict): pass
... 
>>> class OurDict(MyDict, YourDict): pass
... 
>>> OurDict.__mro__
(<class '__main__.OurDict'>, <class '__main__.MyDict'>, <class '__main__.YourDict'>, <class 'dict'>, <class 'object'>)

MyDict の mro は、MyDict->dict->object の順番だったのが、YourDict を定義して、OurDict で MyDictとYourDict を多重継承した場合、MyDict の次の mro が YourDict に変更されていることが分かります。例えば、MyDict の __init__ でスーパークラスの __init__ の呼び出しを super で行った場合、MyDict を修正することなく dict.__init__ から YourDict.__init__ へ変更できるというわけです。

>>> class MyDict(dict):
...   def __init__(self):
...     super().__init__()  # mro により、dict か YourDict かを解決してくれる

本書のサンプルにあった、多重継承の「もう少し複雑なケース」はよく分からなかったので読み飛ばしました。初心者に易しくないのがこわいです。

7章 モジュールとパッケージ

相対インポートと名前空間パッケージ

サードパーティのライブラリをインポートしたり、拡張プラグインを開発するときなど、ちょくちょくはまった経験があります。相対インポートと名前空間パッケージについて簡潔に説明されていて分かりやすいです。PEP 420 で Python3.3 からは名前空間パッケージが言語機能として提供されるようになったようです。

従来の pkg_resources によるおまじないは不要になるのですね。

__import__('pkg_resources').declare_namespace(__name__)

Python のパッケージングの仕組みは、一体どのライブラリの機能なのかよく分からなくなるのがこわいです。

9章 標準ライブラリ

組み込み型関数の memoryview

バッファプロトコルをサポートしたデータを扱い、コピーを作る事なく操作できるクラスです。

こんな組み込み型関数があったんだと初めて知りました。

>>> m = memoryview(b'abc')
>>> type(m)
<class 'memoryview'>

Python2.7 でも (バックポートされて?) 使えるみたいですね。

IO 周りのどんな処理や用途に使うと良さそうなのか、実例を見てみたいです。私はこわくて声をかけられませんが、誰か Python サポーターズに聞いてみてほしいです。

map の特殊な使い方

Python2 では map の第1引数に None を渡すと、足りない要素数を None で補う zip のような挙動をしていました (ライブラリリファレンスの map() の説明) 。

>>> map(None, [1,2,3], [4,5], [7,8,9])
[(1, 4, 7), (2, 5, 8), (3, None, 9)]
>>> zip([1,2,3], [4,5], [7,8,9])
[(1, 4, 7), (2, 5, 8)]

Python3 からはこのように扱ってくれないようです。

>>> list(map(None, [1,2,3], [4,5], [7,8,9]))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'NoneType' object is not callable
>>> list(zip([1,2,3], [4,5], [7,8,9]))
[(1, 4, 7), (2, 5, 8)]

変わりに itertools.zip_longest を使うようです。

>>> from itertools import zip_longest
>>> list(zip_longest([1,2,3], [4,5], [7,8,9]))
[(1, 4, 7), (2, 5, 8), (3, None, 9)]

2to3 では変換できなかったので移行のちょっとした tips ですね。

$ 2to3 map-none.py 
--- map-none.py (original)
+++ map-none.py (refactored)
@@ -1 +1 @@
-map(None, [1,2,3], [4,5], [7,8,9])
+list(map(None, [1,2,3], [4,5], [7,8,9]))

Python サポーターズは 2to3 を使わずに自分でコードを書き直すようです。こわいです。

pdb コマンド

インタラクティブデバッガも標準で付属していて必要十分なのが嬉しいですね。ツールが揃っていると、デバッグも楽しいものです。

  • pretty print

複雑なオブジェクトを調べるには from pprint import pprint してたのですが、pp でできることを知りました (> <)

(Pdb) list
  1     d = {'a': 1, 'b': 2, 'l': range(10), 't': tuple(range(10)), 'd': dict(x=9)}
  2  -> import pdb; pdb.set_trace()
[EOF]
(Pdb) p d
{'t': (0, 1, 2, 3, 4, 5, 6, 7, 8, 9), 'l': range(0, 10), 'a': 1, 'd': {'x': 9}, 'b': 2}
(Pdb) pp d
{'a': 1,
 'b': 2,
 'd': {'x': 9},
 'l': range(0, 10),
 't': (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)}
  • 条件付きブレイク

なぜか私の中では pdb で 条件付きブレイクはできないと思い込んでいました。これも Python2 の頃からできたんですね (- -#)

> /Users/t2y/work/book/review/perfect-python/pdb/pdb_sample2.py(3)<module>()
-> def f(x):
(Pdb) longlist
  1     import pdb; pdb.set_trace()
  2     
  3  -> def f(x):
  4         # do something
  5         return x
  6     
  7     for i in range(5):
  8         print(f(i))
(Pdb) break 5, x==3
Breakpoint 1 at /Users/t2y/work/book/review/perfect-python/pdb/pdb_sample2.py:5
(Pdb) c
0
1
2
> /Users/t2y/work/book/review/perfect-python/pdb/pdb_sample2.py(5)f()
-> return x
(Pdb) x
3
(Pdb) c
3
4
$ 

Python サポーターズの書くプログラムにはバグがないのでデバッガの使う機会はないんだと推測します。こわいです。

column: IDE

PythonIDE でメジャーなものを紹介していますが、あれ!?と思ったのが漏れていたので紹介します。

きっと Python サポーターズは IDE を使っていません。こわいです。

10章 コマンドラインユーティリティ

10-3-5 データを表示する

集計したデータを表示するところで、さらっと operator モジュールが使われていました。

def print_results(results):                                                         
    for key, value in sorted(results.items(), operator.itemgetter(1)):              
        print(key, value)    

operator モジュールの用途の1つとして、要素のゲッターとして使うとコードが簡潔に記述できるので、私は好んでよく使います。

>>> import operator
>>> d = {"x": 1, "y": 2}
>>> operator.itemgetter("x", "y")(d)
(1, 2)

>>> f = open('test.txt', mode='w', encoding='utf-8')
>>> operator.attrgetter('mode', 'encoding')(f)
('w', 'utf-8')

同処理をする Python の関数や lambda を使うよりパフォーマンス面でもメリットがあると、以前はドキュメントに記述されていた気がします。operator, functools, itertools, contextlib, collections といった標準ライブラリを使うようになってきたら段々こわくなる気がします。

12章 アプリケーション/ライブラリの配布

distribute の注意点

distribute には、プロジェクト内のパッケージを自動で発見する find_packages という関数があり、よく利用されています。しかし、Python 3.3 以降の名前空間パッケージが __init__.py を含まなくなったため、注意が必要です。

これもパッケージ配布の機能が標準で提供されることによる過渡期の課題ですね。覚えておくと、移行の tips になりそうです。移行というキーワードで話題を出したのに「昔からそうだよ」と返されるとこわいです。

13章 テスト

doctest の便利な記法

空行を意図する が紹介されています。

def safe_str(s):
    """
    >>> print(safe_str(None))
    <BLANKLINE>
    >>> print(safe_str("test"))
    test
    """
    if s is None:
        return ""
    else:
        return s

その他にも便利な機能がサポートされています。doctest は、コーディングしながら一緒にテストも書ける楽しさがありますね。

  • "..." でマッチングを省略する ELLIPSIS オプション
def myrange(x):                                                                     
    """                                                                             
    >>> list(myrange(10))  # doctest: +ELLIPSIS                                     
    [0, 1, ... 8, 9]                                                                
    """                                                                             
    return range(x)   
  • 空白文字類を無視する NORMALIZE_WHITESPACE オプション
def get_double_range(x):                                                         
    """                                                                          
    >>> get_double_range(10)  # doctest: +NORMALIZE_WHITESPACE                   
    ([0, 1, 2, 3, 4, 5, 6, 7, 8, 9],                                             
     [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])                                             
    """                                                                          
    r = list(range(x))                                                           
    return r, r       

doctest で ELLIPSIS や NORMALIZE_WHITESPACE オプションを使うと、PEP8 の 80 文字制限も遵守できそうでこわいです。

後半の章

後半は主にアプリ開発やツール・ライブラリの紹介です。

  • 14章 Webプログラミング
  • 15章 学術/分析系ライブラリ
  • 16章 マルチメディア
  • 17章 ネットワーク
  • 18章 データストア
  • 19章 運用/監視

本当はいろいろ使ったり、開発したりする上でのコメントも入れたかったのですが、余裕がなくてそこまで至りませんでした。こうやって体系的にみると、いろんな分野で Python が使えるのが分かります。取りあえずで Python を始めてみても、後からあるドメインの処理のために別言語やツールを使わないといけない、といったケースは少ないかもしれません。

2012-04 に Hacker News で好きな言語として Python が選ばれていました。

書くことないから他の記事を引用しているというのが Python サポーターズにばれてないか、こわいです。

正誤表

最後に私が見つけた誤植等をまとめておきました。誰でも編集できるので皆さんも利用して共有してください。Python サポーターズへもこのサイトを連絡済みなので次刷で修正されると思います。

くれぐれも誤植を見つけても悪いのはネコなので文句を言ってはいけません。どんなこわいことがあるか分かりませんからね。

誤植があったらネコのせいです

emerge cat / bengal: 本を書いたらネコがクンスカした

まとめ

無理に Python3 を使う必要はなく、頑に Python2 を使う必要もないのがいまの状況な気がします。新しく開発するものでサードパーティのライブラリがあるなら Python3 で開発を始める時期に差し掛かっていると言えます。

2008年の12月にリリースしたのが Python 3.0 です。
(中略)
リリースから大凡4年がたち、2012年9月にはバージョン 3.3 になっています。

実は前バージョンの 2.0 がリリースされた後、実際に誰もが当然 2 系を使うようになるまで 5 年程度かかっています。各種サードパーティのライブラリが出そろうだけでも多くの時間を要します。Python の原作者である Guido van Rossam 氏は、今回の 3 系に関しても5年かかるだろう、と予測しています。

「2013年 Pythonの本格的な浸透」といった記事も見ましたが、着実に Python3 への移行は進んでいます。Ubuntu の次の LTS では Python3 しか提供しないという噂も聞いたりします。

本書を読んで Python3 プログラミングを始める人が増えて、日本でも Python コミュニティやその開発が活発になると嬉しいです。

Python サポーターズは、自分はこわくないですとみんな言いますが、本当はこわいと思います。

リーダブルコード

うろ覚えですが、学生の頃、プログラミングの勉強をしていて、どこかのサイトに 1000 行以上のコードを書いてみるとその言語のプログラミングの雰囲気が分かるといった内容が書いてあったような気がします。本書は特定の言語に依存した内容ではありませんが、少しは言語依存の内容もあります。

言語仕様を学び、コーディングに慣れてきたら、その次に気になるのが設計だったり、抽象化だったり、人間にとって自然な概念や分かりやすさを表現したくなってきます。本書はそういった人間のための「リーダブルコード」を書くためのコツやその具体例が簡潔にまとめられています。リーダブルコードを書こうと意識するのもまた違ったプログラミングの醍醐味に触れる機会となるでしょう。

個人的におもしろかったところ、気付きがあったところを抜粋して紹介します。

名前重要、超重要!

面倒だからと言って汎用的な名前に逃げない

tmp・retval・foo のような名前をつけるのは、「名前のことなんて考えていません」と言っているようなものだ。このような「空虚な名前」をつけるのではなく、エンティティの値や目的を表した名前を選ぼう。

がーん。

出鼻からくじかれた気分になります。普段から気を付けていることですが、たまに使ってるときもあったりします。私は名前を付けるのが下手で、リファクタリングで名前を変えたりもちょくちょくします。まさに名前付けに王道なしって気がしました。

範囲指定の名前の付け方

これは英語ネイティブではない日本人だと分からない感覚です。素直にそのまま従うと良さそうです。本書では、実際に境界値を含む含まないを図解で解説しているのでさらに分かりやすいです。

  • 限界値を含めるときは min と max を使う
  • 範囲を指定するときは first と last を使う
  • 含有/排他的範囲には begin と end を使う
式を分割する変数の意義
  • 説明変数: 式の意味を説明する変数
  • 要約変数: 式を説明する必要がない場合でも概念を簡潔に表現する変数

この概念が私にとっては目から鱗でした。

式や関数呼び出しを、そのまま関数の引数に渡したり、戻り値として返すコードをよく書いたりしていました。ローカル変数に代入すべきかどうかを迷うときもよくあったのですが、説明したり要約したりする必要があるかどうかで1つの判断基準ができました。

スコープの意識

スコープが小さければ、短い名前でも良い

以前「名前に迷ったら長い名前を書けば良い」というプラクティスを実践していて、何でも長い名前を付けていたときがありました。

ほんの数行から十数行程度の小さなユーティリティ関数なら、関数名で分かりやすさを表現すれば、内部のローカル変数は短い名前でも良いように私も同意します。Python なら PEP8 を守って 80 行文字以内に収めようとするので、以下のようなコードは状況により名前を変えたりもします。

match = re.search(REGULAR_EXPRESSION, text)
if match:
    ...

m = re.search(REGULAR_EXPRESSION, description_to_be_explained_in_detail)
if m:
    ...
Javascript の「プライベート」変数のサンプル

スコープは、言語によって違うので、その言語の仕様やイディオムを理解しておく必要があります。慣れた言語のスコープが頭の中に残ってしまうので、Javascriptクロージャーを用いたサンプルコードの奇妙さからスコープの配慮の重要性が分かりやすかったです。こういったイディオムは、より深く言語を理解する上での取っ掛かりになりますね。

var submit_form = (function () {
    var submitted = false;
    return function (form_name) {
        if (submitted) {
            return ; // 二重投稿禁止
        }
        ...
        submitted = true;
    };
}());

コメントの書き方

私が唯一意識しているコメントの書き方は「意図を表す内容を書く」ということだけでした。本書では、それ以外にも良さそうな実例が紹介されています。

コメントを書かないモチベーション

優れたコード > ひどいコード + 優れたコメント

おもしろい表現です。ひどいコードを説明するのに労力をかけるなら、ちゃんとしたコードを書きなさいという教訓ですね。

ひどいコードにはマーキングする

さらにコードの修正を促すコメントを付けることも恥ずかしがってはいけないとあります。確かに他人のコードを修正するのはやや躊躇してしまうこともあるので、堂々とコードが汚いことを認めるのが良さそうです。よく使う記法です。XXX って危険という意味だったのですね、私は知りませんでした。

記法 典型的な意味
TODO あとで手をつける
FIXME 既知の不具合があるコード
HACK あまりキレイじゃない解決策
XXX 危険!大きな問題がある
分からなかったらググれ

以下のコメントもおもしろいです。

// ベクタのメモリを解放する (「STL swap 技法」で検索してみよう)
vector().swap(data);

まとめ

名前を分かりやすくすること、コードをキレイに保つこと。

リーダブルコードの最初の取っ掛かりです。たくさんコードを書いて、失敗して、工夫して、もっと良い方法を学ぶ。本書は、シンプルなテクニックを紹介していますが、多くのプログラマーの試行錯誤の上で生き残った実践的なテクニックだと思います。知っていることであっても、普段から注意してコーディングしているか、実践しているかを考えるきっかけにも良いと思います。

Subversion で svn:externals (外部参照) を設定する

Subversion は、シンボリックリンクをサポートしています。例えば、あるユーティリティモジュールがあり、それを複数のプログラムから使いたい場合、シンボリックリンクを使うと便利です。

シンボリックリンクの作成

試してみましょう。テスト用のリポジトリを作成して Subversion サーバーを起動します。

$ svnadmin create svn-repos
$ vi svn-repos/conf/svnserve.conf 
[general]
anon-access = write  # 匿名書き込みアクセスを許可する
$ svnserve --daemon --foreground --root svn-repos/

サーバーが起動しました。クライアントからチェックアウトします。

$ svn co svn://localhost/ ./myrepo
リビジョン 0 をチェックアウトしました。

$ # 初期ディレクトリとシンボリックリンクを作成
$ cd myrepo
$ mkdir utils tool1 tool2
$ vi utils/myfunc.py
print "do something"
$ (cd tool1; ln -s ../utils/myfunc.py)
$ (cd tool2; ln -s ../utils/myfunc.py)
$ ls -l tool1 tool2
tool1:
lrwxrwxrwx 1 t2y t2y 18  98 12:00 myfunc.py -> ../utils/myfunc.py
tool2:
lrwxrwxrwx 1 t2y t2y 18  98 12:00 myfunc.py -> ../utils/myfunc.py

$ svn add utils tool1 tool2
A         utils
A         utils/myfunc.py
A         tool1
A         tool1/myfunc.py
A         tool2
A         tool2/myfunc.py

$ svn commit -m "added"
追加しています              tool1
追加しています              tool1/myfunc.py
追加しています              tool2
追加しています              tool2/myfunc.py
追加しています              utils
追加しています              utils/myfunc.py
ファイルのデータを送信しています ...
Committed revision 1.

# リポジトリの属性を調べる
$ svn proplist -R .
'tool1/myfunc.py' の属性:
  svn:special
'tool2/myfunc.py' の属性:
  svn:special

シンボリックリンクを作成すると、svn:special という属性が設定されます。

別のクライアントからチェックアウトしてみましょう。

$ svn co svn://172.16.55.131/ ./myrepo2
A    myrepo2/tool2
A    myrepo2/tool2/myfunc.py
A    myrepo2/utils
A    myrepo2/utils/myfunc.py
A    myrepo2/tool1
A    myrepo2/tool1/myfunc.py
リビジョン 1 をチェックアウトしました。

$ cd myrepo2/
$ ls -l tool1 tool2
tool1:
lrwxrwxrwx 1 t2y t2y 18  98 12:09 myfunc.py -> ../utils/myfunc.py
tool2:
lrwxrwxrwx 1 t2y t2y 18  98 12:09 myfunc.py -> ../utils/myfunc.py
$ cat tool1/myfunc.py 
print "do something"

ちゃんとシンボリックリンクが有効になっていますね。

svn:externals (外部参照) 属性の設定

Unix 系 OS であれば、これで大丈夫ですが、Windows ユーザーも使いたいときに課題があります。

But that doesn't in any way limit the usability of working copies on systems such as Windows that do not support symlinks. On such systems, Subversion simply creates a regular text file whose contents are the path to which the original symlink pointed. While that file can't be used as a symlink on a Windows system, it also won't prevent Windows users from performing their other Subversion-related activities.

シンボリックリンクのバージョン管理

Windowsシンボリックリンクをサポートしていないため、リンク先を指すテキストファイルを作成して、ユーザーの Subversion 操作においては邪魔しないとあります。

実際に Windows 環境でチェックアウトしてみましょう。

チェックアウト結果をみると、ファイルをコピーして作成してくれているように見えますが、ファイルの中身は "link ../utils/myfunc.py" です。ドキュメント等であれば良いですが、ユーティリティモジュールなどでは使えないですね。

そんなとき、Windows 環境でもシンボリックリンクのように扱える仕組みが svn:externals 属性です。

Fortunately, Subversion provides support for externals definitions. An externals definition is a mapping of a local directory to the URL―and ideally a particular revision―of a versioned directory. In Subversion, you declare externals definitions in groups using the svn:externals property. You can create or modify this property using svn propset or svn propedit (see the section called “Manipulating Properties”). It can be set on any versioned directory, and its value describes both the external repository location and the client-side directory to which that location should be checked out.

svn:externals 属性のドキュメント

svn:externals のドキュメントによると、リポジトリ内に外部リポジトリの URL を指定したり、そのリポジトリ特定バージョンを取得するように設定できるとあります。もともとは外部のリポジトリを取り組むための機能のようですが、同じリポジトリ内においても利用できます。そして、svn propsetsvn propedit で設定できると記述されていますが、残念ながら実際の設定コマンドのサンプルがここでは記述されていません。そのため、どうコマンドを実行して良いか全く分かりません (> <)

svn:externals の設定は直感的に分かりにくいので備忘録としてまとめておきます。また、Suversion 1.4 でディレクトリ単位、1.6 からファイル単位の外部参照ができるようになったので svn クライアントのバージョンにも注意してください。

$ mkdir tool3
$ svn add tool3
A         tool3

$ cd tool3
$ svn propset svn:externals "../utils/myfunc.py linked_myfunc.py" .
属性 'svn:externals''.' に設定しました

$ svn update
Updating '.':
Fetching external item into 'linked_myfunc.py':
A    linked_myfunc.py
外部項目をリビジョン 5 に更新しました。
リビジョン 5 です。

$ cat linked_myfunc.py 
print "do something"

$ svn commit -m "set svn:externals"
送信しています              .
Committed revision 6.

svn propset svn:externals の引数が覚えられないです (> <)
1つのやり方としては、以下のように相対パスで指定します。

$ svn propset svn:externals "カレントディレクトリからの相対パス  取得後のファイル名" 属性設定したいディレクトリ

svn:externals 属性は、ディレクトリに対して設定されます。

$ svn proplist tool3/linked_myfunc.py 
$ svn proplist tool3
'tool3' の属性:
  svn:externals

別のやり方として、リポジトリのルートパスからの相対パスを指定する方法もあります。今度は同じファイル名 myfunc.py で取得しています。

$ svn propset svn:externals "^/utils/myfunc.py myfunc.py" tool4
属性 'svn:externals''tool4' に設定しました
$ svn update tool4/
Updating 'tool4':
Fetching external item into 'tool4/myfunc.py':
リビジョン 7 の外部項目です。

リビジョン 7 です。
$ svn commit -m "set svn:externals"
送信しています              tool4
Committed revision 8.

Windows 環境でもチェックアウトしてみます。

Unix 系 OS 環境と同様にファイル本体がチェックアウトできました!

外部リポジトリ特定ディレクトリを取得したいときは URL を指定します。こういうときは取得した後のディレクトリ名を変更できると便利です。

$ mkdir svn
$ svn add svn/
A         svn
$ svn propset svn:externals "http://svn.apache.org/repos/asf/subversion/trunk/tools/examples/ tool-example" svn/
属性 'svn:externals''svn' に設定しました

$ svn update
Updating '.':

Fetching external item into 'tool3/linked_myfunc.py':
リビジョン 10 の外部項目です。

Fetching external item into 'tool4/myfunc.py':
A    tool4/myfunc.py
外部項目をリビジョン 10 に更新しました。

Fetching external item into 'svn/tool-example':
A    svn/tool-example/svnserve-sgid.c
A    svn/tool-example/svnput.c
A    svn/tool-example/dumpprops.py
A    svn/tool-example/svnlog2html.rb
A    svn/tool-example/check-modified.py
A    svn/tool-example/svnlook.py
A    svn/tool-example/svnshell.py
A    svn/tool-example/geturl.py
A    svn/tool-example/getlocks_test.c
A    svn/tool-example/info.rb
A    svn/tool-example/svnlook.rb
A    svn/tool-example/get-location-segments.py
A    svn/tool-example/headrev.c
A    svn/tool-example/putfile.py
A    svn/tool-example/revplist.py
A    svn/tool-example/svnshell.rb
A    svn/tool-example/minimal_client.c
A    svn/tool-example/getfile.py
A    svn/tool-example/blame.py
A    svn/tool-example/SvnCLBrowse
A    svn/tool-example/testwrite.c
外部項目をリビジョン 1382235 に更新しました。

リビジョン 10 です。

svn update を実行する度に fetch 処理が実行されます。仕方ないですが、外部リポジトリを参照すると実行に時間がかかるので、更新をチェックしたくないときは "--ignore-externals" オプションを指定します。

$ svn update
Updating '.':
Fetching external item into 'tool3/linked_myfunc.py':
リビジョン 11 の外部項目です。
Fetching external item into 'tool4/myfunc.py':
リビジョン 11 の外部項目です。
Fetching external item into 'svn/tool-example':
リビジョン 1382235 の外部項目です。

リビジョン 11 です。

$ svn update --ignore-externals
Updating '.':
リビジョン 11 です。

あまり頻繁に使う機能ではありませんが、使いたいときに実際の設定コマンドのサンプルを見つけられなくて苦労しました。

ちなみに Subversion もいまや Apache プロジェクトのトップレベルプロジェクトなんですね。ホームページも刷新されていて驚きました。

2012/9/10 追記

同じディレクトリに複数ファイルの svn:externals 属性を設定するには、svn propedit を使わないと設定できないようです。

$ svn propedit svn:externals tool5 --editor-cmd=vim  # vim エディタで複数ファイルを設定
^/utils/myfunc.py myfunc.py
^/utils/myfunc2.py myfunc2.py

属性 'svn:externals' の新しい値を 'tool5' に設定しました

アリエル・ネットワークに入社しました

9月からアリエル・ネットワーク (以下アリエル) に入社しました。

以前、アルバイトで3ヶ月半ほどお仕事していました。以下はそのときの手記です。

さて、最近こんなツイートを見ました。


どうやら私がアルバイトをしていた頃から少し経って、最近たるんでいるようです。

これはまた締め直さなければ、、、

と思っていたら入社の初日から先制攻撃を受けてしまいました。ほんとに怖い会社です、、、

寝坊したんじゃないです!初日に入館できる手続きをするから9時に来てと言われてたんです (> <)、朝組イメージを払拭するために、朝何時に来ようが本当はどうでも良いです。生産性を上げるには、人によっては (私もそうですが) 朝型で取り組む方が効率的だったりもしますが、それよりも (自分にあった) 規則正しい生活を続ける方が安定したパフォーマンスを発揮できて、結果的に生産性が上がります。

閑話休題。私はこれまでフリーランスの経験もあわせると7社で働いてみました。それぞれの会社の特徴や文化の一長一短があり、どの会社が優れているとは一概に言えませんが、どの会社が自分の考え方やキャリアの方向性と最も一致しているかということは言えます。私はやはりプログラマーとしてのキャリアをもっと突き詰めたいし、チームメンバーよりもコードを書いて、チケットをクローズしまくるマネージャーが普通にいるアリエルのような職場が魅力的でした。

エンタープライズ分野のパッケージ販売という、ソフトウェア開発の王道とも言える業務に対して、これまでの経験を活かして何ができるか。結果的に過去のキャリアでは、最長3年と、中長期的な展望をもって開発に取り組むことができなかったことが私の中での課題であり、未知の領域でした。それは結果論であって本意ではなかったのですが、次に働くところは中長期的な展望をもって開発に取り組める企業にしようと決めていました。

ひとつ自己努力だけで容易に学べないものがあると思っています。良い開発プロセスを経験できるかどうかです。良い開発プロセスとはウォーターフォールできっちりやるという意味ではありません。社内の開発者がどれだけ合理的にプロセスを改善しようとしているかが重要です。会社にそういう文化があるかどうかです。開発プロセスは日々の行動の積み重ねなので、勉強会で話を聞いたり本を読むだけでは限界があります(わかった気にはなれますが)。

2013年度、アリエル新卒募集が始まりました | ありえるえりあ

実際、私が働いた7社の中で最も優れた開発プロセスをもっていたのもアリエルでした。開発プロセスの改善というのは、技術力やその知識以上に、会社の財務基盤や業務としての開発文化、そして上司の理解が必要になります。いわゆる wikipedia:技術的負債 を抱え込まないために必要なことは誰もが分かってますが、中長期的な開発計画がないと、そもそもそんな投資はできません。また、短期的な利益とのトレードオフになるため、そのバランスを取ることが難しいです。

これまで、私は独りで開発してきたことが多かったので、チームでの開発プロセス改善やその在り方を模索していけることも今後の楽しみです。

最後に1つ。

エキスパート職に関する仙石さんの見解は単純かつ明解でした。エキスパート職の職能は、まわりに良い影響を与えられるか、だと言いきりました。

マネージャになりたくないプログラマのキャリアパス | ありえるえりあ

これは私の中の「すごい人」理論にも通じます。

どこの会社にも優秀な方はたくさんいますが、本当にすごい人はあまりいません。職場にすごい人なんか滅多にいません。私が思うすごい人は、会社の枠や多少の損得なんかの概念を超えて、さらなる高みへ行ってしまうような人たちです。別の言い方をすると、自分でリスクを取って行動し、結果を出しても、さらに先を目指すような人たちです。

アリエルにはそういった人たちが何人もいますし、私もそんな人たちを目標に切磋琢磨していきたいと考えています。

結局のところ、何かおもしろそうだ、という直感は生きていく上で大事なものなんだと思います。