空白の意味付け
Pythonを使い始めたときに、他の言語との大きな違いとして一目瞭然なのが、空白による字下げに重要な意味付けがされている点です。C#では、空白による字下げは文法的に重要な意味を持ちません。つまり、ステートメントの末尾はセミコロン(;)で示し、スコープのブロックは中かっこ({})で示します。一方VBでは、行末が自動的にステートメントの末尾となりますが、スコープのブロックの末尾は明示的に示します。キーワードEndを使うもの(End Sub、End Function、End Classなど)が大半ですが、ブロックの意味に応じたキーワードを使うもの(Do/Loop、For/Nextなど)もあります。
これとは対照的に、Pythonでは、ステートメントの末尾とスコープのブロックの区切りの両方で、空白が重要な意味を持ちます。行末が自動的にステートメントの末尾になる点はVBと同様です。また、スコープのブロックの先頭をコロン(:)で明示的に示す点はC#の左中かっこ({)に似ています。しかし、VBやC#と違い、コードブロックの末尾は、インデントに基づいて暗黙的に判断されます。つまり、インデントの深さが同じコードは、同じコードブロックに属するものとみなされます。具体例で見てみましょう。バブルソートのアルゴリズムをPythonで実装すると、次のようになります。
def bubble_sort(ar):
l = len(ar) - 1
for i in range(l):
for j in range(l-i):
if ar[j] > ar[j+1]:
temp = ar[j]
ar[j] = ar[j+1]
ar[j+1] = temp
return len(ar)
このコードにはスコープのブロックが4つあります。bubble_sort関数本体と、2つのforループ(PythonのforループはC#のforeachループに相当、range関数はEnumerable.Rangeに相当)、および1つのifブロックです。各ブロックはインデントの深さがそれぞれ異なります。たとえば、returnステートメントは1つ目のforループとインデントの深さが同じです。したがって、forやifのスコープのブロックではなく、関数のスコープのブロックに属することになります。この例ではインデントに4文字分の空白を使っていますが、インデントの深さそのものはいくつでもかまいません。肝心なのは深さが同じかどうかです。
空白によるインデントが意味を持つことは、コードの一貫性と読みやすさの両面でメリットがあります。C言語の構文で記述するC#のような言語では、コードの書き方に関する考え方や嗜好がプログラマごとに異なります。たとえば、中かっこをどこに配置するか、タブと空白のどちらを使うか、インデントをいつ使うか、どの深さまでインデントするか、といった面です。C#では空白は無視されるため、コードの整形方法は無数にあります。一方Pythonでは、規則にのっとってコードを整形する方法は1つしかありません。つまり、面識のない人が書いたPythonのコードを無作為に取り出したとしても、自分が書いたPythonのコードとまったく同じスタイルで整形されていることになります。このため一般には、Pythonのコードの方が読みやすさは上です。また、セミコロンや中かっこといった余分な要素を使った構文でステートメントの末尾やスコープのブロックの範囲を示さなくてよいという点も、読みやすさの向上に寄与します。
空白によるインデントが意味を持つPythonの表記法は、開発者によって好き嫌いが分かれる部分です。しかし、同様の特徴を持つ言語は増えつつあるようです。たとえば.NET用のオープンソースの言語であるBooやCobraも、Pythonの流れをくんだ構文を採用しており、空白が意味を持ちます。また、Microsoftの新しい言語であるF#にも、Pythonと同様にインデントが意味を持つ軽量な構文のモードがオプションで用意されています。
動的型付け
空白によるインデントが意味を持つという点は、PythonとC#/VBのコードの外見上の大きな違いですが、内容的に最も大きな違いと言えるのは、型の扱い方です。命令文、関数、クラス、オブジェクトといった高水準言語の概念は、.NET向け言語と同様にPythonにも備わっています。ただし、C#やVBといった言語は静的型付け言語であり、個々の型が持つ機能はコンパイル時に確定します。型のフィールド、プロパティ、メソッドを定義し、コードをコンパイルしたら、それらの機能を実行時に変更することは不可能です。変更を加えたければ、コンパイルからやり直すしかありません。
一方、動的型付け言語の場合、型の機能はコンパイル時点では確定しません。型は完全に可変であり、実行時にも操作できます。従来でいえば、型のインスタンスを実行時に操作するのと同じような感じです。たとえば、新しい型を作成したり、型に対してフィールドやメソッドの追加や削除を行ったり、型の特定のインスタンスに対してフィールドやメソッドを追加したり、型の継承の階層を変えたりといったことまで実行時に操作できます。
静的型付け言語に慣れ親しんできた開発者にとっては、型そのものを実行時に操作できるのは奇妙に思え、危険にすら感じられるかもしれません。静的型付けを推す人がその主な優位性として指摘することが多いのは、タイプセーフという面です。静的型付けを利用する場合、作成した型および呼び出し先のメソッドが存在するかどうかや、フィールドおよびパラメータが正しい型かどうかなど、かなりの検証をコンパイラが行うことができます。こうした検証は動的型付け言語では不可能です。Pythonの場合、この種のエラーは実行時例外となります。
しかしここで、このタイプセーフというメリットを大きな視点で考えてみましょう。それなりの規模のシステムとなると、アプリケーションで不具合が生じる原因や背景は無数にあり得ます。タイプセーフな言語で解決できるのは、そのうちのたった1つに過ぎません。逆に言うと、システムの開発時に紛れ込むエラーやバグの大多数は、静的型付け言語のコンパイラであっても検出できません。仮にすべてのエラーを検出できるとしたら、正常にコンパイルできたアプリケーションにはバグは絶対にない、という話になってしまうからです。実際にはそんなことはありません。コンパイル時点で検出されなかったエラーを、自動単体テストなどの手法で見つけることになります。このように、静的型付けとは、安全網の1つになるのは確かですが、決して完全無欠ではないのです。
ただし私は、タイプセーフな言語は駄目だと言っているわけではありません。静的型付けによるタイプセーフという面と、動的型付けによる柔軟性という面のどちらを取るかを、天秤にかけて判断すればよい話だと思っています。どちらか一方の答えが絶対的に正しいということはありません。開発者本人やプロジェクトにとってどちらが適切か、ということでしかないのです。ともあれ、IronPythonのような動的言語がMicrosoftの言語のラインナップの中に加わったおかげで、.NET環境の中でそうした選択の余地ができたのは、うれしいことです。
