トップPython入門 > Python入門

Python入門

Pythonとは

Python(パイソン、にしきへび)は、代表的なプログラミング言語のひとつである。 誕生は 1991年とされている[1]。JavaScript(前身はLiveScript)誕生の数年前になるが、 概ね同世代に誕生したスクリプト言語と言えよう。

日本ではあまり使われていないが、米国では人気が高いスクリプト言語のひとつである。 多くのWebサイトの開発に用いられている。 Google社では Java、C++、Python を社内で用いる3大言語としている。

このパソコンには
  Python 2.7.9 (default, Dec 10 2014, 12:24:55) [MSC v.1500 32 bit (Intel)] on win32
をインストールしている。c:\Python27 にダウンロードして、Path に登録している。 Python 2.7系の最新リリースは Python 2.7.10 である。

また、現時点では Python 3.5正式版もリリースされている。 しかし、上位互換性がないようだ。 つまり、Python 2 用のプログラムは Python 3 では動作しないケースがあるので、使用者がまだ少ないらしい。 必要に迫られない限り、Python 2.7.9 を使い続ける予定である。

プログラムの実行

コマンドプロンプトで実行する場合には、python を引数なしで実行すればよい。

c:\sys>python
Python 2.7.9 (default, Dec 10 2014, 12:24:55) [MSC v.1500 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> print "hello"
hello
>>>

C言語の printf関数と同じように、書式付きでprintすることができる。

>>> print "%s %d日目" % ("Python学習", 3)
Python学習 3日目
>>>

プリントするのではなく、文字列を文字列変数にセットするには次のようにする。

  str = "%s %d日目" % ("Python学習", 3)

C言語で

  sprintf(str, "%s %d日目", "Python学習", 3);
と書くことに相当する。Python の場合、 %s, %d など書式の % と書式と引数の間に置かれる記号が同じため、 コードが見づらい。

なお、sprintf の場合、格納する文字列の長さが文字配列strのサイズを超えないことを保証されていなければ ならない。VC の場合、次のように sprintf_s を使う方が無難である。

  sprintf_s(str, sizeof(str), "%s %d日目", "Python学習", 3);

Python実行終了は ^Z(Ctrl-Z)または exit() とする。

ソースプログラムを実行するには、次のようにする。

最初に、文字列「hello world!」 を表示してみよう。次のように、1行だけのプログラム test01.py を作成する。

[test01.py]
print "hello world!"

プログラム・ファイル名(パス)を引数として python を実行する。結果を下に示す。

c:\sys>python test01.py
hello world!

Pythonプログラムをバイトコンパイルする

何度も実行するプログラムの場合、バイトコンパイルするによって、パフォーマンスを改善できる。 例えば、次のプログラム hello.py

#!/usr/bin/env python
print 'hello'
をコンパイルするには、次のようにすればよい。
c:\sys>python -m compileall hello.py
Compiling hello.py ...

コンパイルされたコードのファイル名の拡張子は .pyc である。 上の例では hello.pyc が生成される。

このコードは

c:\sys>hello.pyc
hello
または
c:\sys>python hello.pyc
hello
とすることにより実行できる。

実行時間がどう変わるか、次のプログラムで調べた。 if __name__ == '__main__': がメイン関数に当たる。変わった書き方をするものだ。

import time

if __name__ == '__main__':
    start = time.time()
    sum = 0
    for i in range(1,100001):
        sum = sum + i*2
    print 'sum=' + str(sum)
    elapsed_time = time.time() - start
    print('elapsed_time:{0}[sec]'.format(elapsed_time))

コンパイル・実行結果は次のようになり、どういうわけか、 実行時間はほとんど変わらなかった。

このプログラムは非常に簡単であるから、コンパイルに要する時間は極めて短い。 したがって、ばいとコンパイルがパフォーマンス向上に寄与するのは、 プログラム行数が膨大であり、繰り返し処理があまりなく実行時間が短いプログラムであろう。

タイル地図作成のPythonプログラムの高速化に役立つかと思ったが、その可能性はなさそうである。

c:\sys>python test.py
sum=10000100000
elapsed_time:0.095999956131[sec]

c:\sys>python -m compileall test.py
Compiling test.py ...

c:\sys>python test.pyc
sum=10000100000
elapsed_time:0.0969998836517[sec]

現在のタイル地図作成は下記の minZoom, maxZoom, bbox の数値を変えて、 コンパイル&実行を繰り返している。

これらの数値をファイルから読み込み実行するように変更すれば、 コンパイルは一度だけで済むので特定のZoomでは相当な高速化が期待される。

しかし、タイル地図作成そのものにCPU時間がかかるため、総合的には タイル地図作成時間がそれほど短縮されない可能性が高いが、 Pythonの勉強としてやってみよう。

要するに、Pythonは遅い。Mapnikのカーネルは C++ で開発されているので、 上位も Python から C++ あるいは C# など高性能言語に書き変えれば、 大幅な高速化が期待される。 しかし、そう簡単に移植できそうにないので、Pythonプログラムを使い続けている。

if __name__ == "__main__":
    home = "C:\mapnik-stylesheets-master"
    mapfile = "my_osm.xml"
    tile_dir = "c:/gisdata/tiles/osmx/"

    minZoom = 10
    maxZoom = 18
    bbox = (135.49, 34.796, 135.59, 34.818)
    render_tiles(bbox, mapfile, tile_dir, minZoom, maxZoom, "Test")

[2016.5.26追記]

Pythonに触れて1年余り経ち、Pythonプログラムに目が慣れてきた。 Pythonは、記法にクセがあるので、最初は戸惑ったが、その気になって調べてみると、 C# のようなオブジェクト指向言語とほとんど違いがないことが分かった。

現在のタイル画像作成プログラムのオーバヘッドを大幅に削減するためと、 使いやすさを向上するために、元のプログラムを大きく改造した。 そのソースコードを本ページの末尾に掲載している。

まだ、多少は性能改善の余地があるかも知れないが、 タイル地図画像作成そのものに時間がかかることから、大幅な性能向上は望めない気がする。

下のプログラムでは、コマンドライン引数で指定されたディレクトリにタイル画像を置いている。 地図システムとしては 32x32タイルを一つのブロックとして、ZIPアーカイブとしている。 この ZIPアーカイブ化を Pythonプログラム内で実行すれば、数%〜10%程度の性能向上の可能性があるが、 年数回のタイル地図の部分更新では、ZIPアーカイブを一から作成するのではなく、更新されたタイルについてのみ ZIPアーカイブのエントリを書き変える。更新タイルを限定するすることにより、地図更新時間の短縮を図る。

つまり、一旦タイル画像を作成した後、更新があったかどうか調べてから、必要に応じて、 元のエントリを削除してから新しいエントリを追加する。

このような諸々の処理を Python プログラムに含めるのは、オーバヘッドの増大を招く。 このため、Pythonプログラムは部品として使いやすいように、仕様を単純にしている。

基本文法

次章のプログラムを殆ど理解せず使ってきた。 先ずは、このプログラムに出てくる記述を理解してゆこう。

インデントに特徴

C言語など構造化プログラミング言語では、スペースや改行などに意味がないので、 プログラム

    if (x > 0) 
        x = 5;
    y = 0;
と次のプログラムは同じである。
    if (x > 0) 
        x = 5;
        y = 0;
また、次のように書くこともできる。
    if (x > 0) x = 5;
    y = 0;

これに対して、Python はインデントに意味があり、 プログラム

    if (x > 0) :
        x = 5
    y = 0
と下のプログラムは別物である。
    if (x > 0) : 
        x = 5
        y = 0

上のプログラムは、C言語では

    if (x > 0) x = 5;
    y = 0;
であり、下のプログラムは
    if (x > 0) {
        x = 5;
        y = 0;
    }
である。

C言語などに比べて、自由度が少ないため、誰が書いても似たようなプログラムとなり、 プログラムが分かりやすい、と言われている。

見かけ上同じでもタブとスペースは別物のため、混在させるとエラーとなる。

文字列

文字列は "hello" あるいは 'hello' のように、二重引用符または一重引用符でくくる。 JavaScriptやHTMLなどと同じである。

下の例に示すように、文字列連結演算子は 「 + 」である。 文字列の先頭部および末尾部の空白(スペース文字、タブ、改行)を除くには strip() を使用する。 Java、C#、PHP などでは trim という名称が使われる。

if __name__ == "__main__": 
    str = " \tabc\n"
    print "<" + str.strip() + ">"
c:\sys>python c:\mh\www\python\src\strip.pi
<abc>

エスケープ \r、\n、\t、\\ などのエスケープ文字の書き方は概ね C言語と同じようだ。 もし、違いが見つかれば、そのとき追記する。

数値型

主な数値演算を下表に示す。この表は Python 3 のマニュアルから抜粋したものなので、Python 2 では異なるかも知れない。 どんな言語にせよ、使いながら覚えていくことになろう。

あるページに Python2.7 の int, float は C言語の long, double、 Python2.7 の long はデータ幅無制限とあった。 厳密な記述ではないと思うので、いずれ調べよう。
演算結果注釈
x + y x と y の和
x - y x と y の差
x * y x と y の積
x / y x と y の商 (1)
x % y x / y の剰余
abs(x) x の絶対値または大きさ
int(x) x の通常整数への変換 (2)
long(x) x の長整数への変換 (2)
float(x) x の浮動小数点数への変換
complex(re,im) 実数部 re 、虚数部 im の複素数。 im のデフォルト値はゼロ。
c.conjugate() 複素数 c の共役複素数
pow(x, y) x の y 乗
x ** y x の y 乗
注釈

(1)
整数の割り算では、結果はマイナス無限大の方向に丸められる。 つまり、1/2 は 0、 (-1)/2 は -1、1/(-1) は -1、そして (-1)/(-2) は 0 になる。
(2)
浮動小数点数からの変換では、C言語と同様の 値の丸めまたは切り捨てが行われるかもしれない。 きちんと定義された変換については、math モジュールの floor() および ceil() を使用する。

print float("1.234e-4") は 0.0001234 となった。指数表記もサポートされている。

繰り返し

「for」文ではシーケンス型のオブジェクトから要素を取り出しながら繰り返し処理を行う。 指定した回数だけ繰り返しを行いたい場合には range関数を使って連続した数値の要素を持つリスト型のオブジェクトを作成して利用する。

下にプログラム例を示す。 range関数は終了の値を含まない。 したがって、これで 1 から 10 までの足し算となる。 終了の値を含まないところに注意を要する。

sum = 0
for num in range(1, 11):
  sum += num
print "sum = " + str(sum)

数値を文字列に変換する

数値を文字列に変換するには次のように記述する。

   zoom = "%s" % z

書式指定子が %s の場合は

   zoom = str(z)
としてもよい。この方が分かりやすい気がする。

self

クラスのコンストラクタの書き方がJavaやC#など通常のオブジェクト指向プログラミング言語と 少し異なるので注意がいる。

コンストラクタは __init__() と書く。 コンストラクタの先頭引数は self とするのが慣例である。これは Java や C# などの this に当たる。 つまり、self はクラスのインスタンスを表す。

引数を明示するため、self でなくて別の名前でも動くと思われるが、self とするのが慣例のようだ。 つまり、Java や C# などはコンストラクタに this という引数は書かないが、 Python では必要となることが異なる。

class GoogleProjection:
    def __init__(self,levels=18):
	[以下省略]

コンストラクタの呼び出しでは、この先頭引数の self は記述しない。

コンストラクタだけでなく、メンバ関数でも先頭引数を self とする。 呼び出しでは、この引数を省略する。

    def fromPixelToLL(self,px,zoom):

静的メソッドの場合は、暗黙の第一引数はない。

class C:
    @staticmethod
    def f(arg1, arg2, ...): ...

import

次の3つの表現がある。

import os はモジュール os をインポートしている。

import mapnik2 as mapnik はモジュール mapnik2 をインポートして名前を mapnik に変更して使う。

from math import log,exp,atan はモジュール math から log, exp, atan 関数だけをインポートする。 もし、import math とした場合には math.log(x) としなければならないところが、log(x) で済む。

import sys, os
import mapnik2 as mapnik
from math import pi,cos,sin,log,exp,atan

日本語

日本語コードは一般には UTF-8 を使うのが無難である。 この場合はプログラムの頭に # -*- coding: utf-8 -*- を置く。

このホームページでは Shift_JIS を使っている。 その場合は、「try,except,finally」節の例にあるように # -*- coding: shift_jis -*- を置く。

try,except,finally

Java、C#、VBA などの例外処理構文は try, catch, finally であるが、 Python では try, except, finally である。catch と except にはどんな違いがあるのだろうか?

Pythonプログラムはほとんど経験がないが、今のところ、両者は同じに思える。 try ブロックの途中で例外(エラー)が起きた場合にのみ、except ブロックが実行される。

一方、finallyブロックは例外が起きても、起きなくても必ず実行される。 しかし、それならば finally ブロックを使わず、次の実行文とした場合と何が違うのだろうか?

例えば、try ブロックで確保したリソースを解放する処理などは finally ブロックに書く方が 分かりやすいことは確かである。プログラムの分かりやすさのためか、 それとも finallyブロックには、何か自動的に行われるご利益があるのだろうか?

最近は C# プログラムがメインで、そこでは、try, catch は使っているが、 finally は使っていないので、目下のところ正確なことは分からない。 必要が生じた時に調べることとする。

まず、下に示す文献[3]の例を試してみよう。

# -*- coding: shift_jis -*-

def exception_test(value_1,value_2): 
    print "====計算開始===="
    result = 0 
    try: 
        result = value_1 + value_2 
    except: 
        print "計算出来ませんでした!" 
    finally: 
        print "計算終了" 
    return result 

if __name__ == "__main__": 
    print exception_test(100,200) 
    print exception_test(100,"200") 

コンパイル&実行結果を下に示す。

c:\sys>python c:\mh\www\python\src\exception_test.pi
====計算開始====
計算終了
300
====計算開始====
計算出来ませんでした!
計算終了
0

先に述べたように、exceptブロックは例外が起きた場合にのみ実行され、finallyブロックは必ず実行される。

実例として、OSMタイル地図作成プログラムには次の記述がある。 except KeyError では、文字列コードの誤りを指すようだ。 ただし、先頭で宣言した日本語コードとプログラムファイルのコードが違う場合には、 コンパイル段階でエラーが検出されるので、ここは実行時に不正コードを含む文字列を合成した場合の エラー検出と思われる。

if __name__ == "__main__":
    home = "C:\mapnik-stylesheets-master"
    try:
        mapfile = "my_osm.xml"
    except KeyError:
        mapfile = home + "/svn.openstreetmap.org/applications/rendering/mapnik/osm-local.xml"
    try:
        tile_dir = "c:/gisdata/tiles/osmx/"
    except KeyError:
        tile_dir = home + "/osm/tiles/"

このプログラムでは、簡単な文字列を変数に代入しているのに過ぎない。 ここでエラーが起きることまで考えると、他の場所にも同等の例外処理を入れないとつり合いがとれない。 それでは、例外処理だらけの分かりにくいプログラムになってしまう。 また、このパソコンの実装では、例外時に参照される上記のディレクトリは存在しない。 例外処理はエラーメッセージを出して、終了するように変更しなければならない。

このため、例外処理はカットして、下のようにプログラムを書き変えている。

if __name__ == "__main__":
    home = "C:\mapnik-stylesheets-master"
    mapfile = "my_osm.xml"
    tile_dir = "c:/gisdata/tiles/osmx/"

先頭の #!/usr/bin/env python は環境変数 PATHのなかに含まれているpythonを探して実行する、 という意味のようだ。 Windowsパソコンでは Python に限らず、どんなプログラムを動かす場合もそうなるから、 この行はなくてもいいはず。他のOSでの実行を考えて、記述しておく方がいいだろう。

except KeyError は、 マップ型 (辞書型) オブジェクトのキーが、オブジェクトのキー集合内に見つからなかった場合に送出される。

なぜ、メインプログラムで try がいるのだろう。mapfile が単なる変数ならば例外が起こるわけがない。 home ディレクトリ中に mapfile が無ければ、例外が起こるという意味なのだろうか?

このプログラムでは、mapfile は home ディレクトリ上のファイル名ということが、規定されるのだろうか。

    home = "C:\mapnik-stylesheets-master"
    try:
        mapfile = "my_osm.xml"
    except KeyError:
        mapfile = home + "/svn.openstreetmap.org/applications/rendering/mapnik/osm-local.xml"

何事も、最初は分からないことだらけ。それが、月日と共に、少しずつ謎が解けてゆくものだ。 こんなときは小さなプログラムを書いて確かめればよい。

mainプログラムだけ切り出して実行してみた結果、try 〜 except KeyError は無意味と思われる。 また、

    if not tile_dir.endswith('/'):
        tile_dir = tile_dir + '/'
も要らないから、事実上のmain関数は次のように書けばいいということであろう。
if __name__ == "__main__":
    mapfile = "my_osm.xml"
    tile_dir = "c:/gisdata/tiles/osmx/"

    minZoom = 10
    maxZoom = 18
    bbox = (135.49, 34.796, 135.59, 34.818)
    render_tiles(bbox, mapfile, tile_dir, minZoom, maxZoom, "Test")

リスト(配列)

Pythonには配列はない。代りにリストを使う。 Pythonは元々オーバヘッドが大きいため、制約を付けても実行時間の短い配列を実現できないだめだろう 、というのが憶測である。

リストのサイズは len(listname) である。

リファレンス

[1] Python
[2] Mapnik
[3] 例外処理
[4] Pythonで例外処理を扱う(try,except)

タイル画像作成プログラムソース

OSMのタイル地図作成プログラムを下に示す。

#!/usr/bin/env python
# -*- coding: shift_jis -

# C:\mapnik-stylesheets-master>python gentile.py 14 x_y14.txt d:/tiles

from math import pi,exp,atan

try:
    import mapnik2 as mapnik
except:
    import mapnik

import sys, os
from Queue import Queue
import threading

RAD_TO_DEG = 180/pi
NUM_THREADS = 4

class GoogleProjection:
    def __init__(self,levels):
        self.Bc = []
        self.Cc = []
        self.zc = []
        self.Ac = []
        c = 256
        for d in range(0,levels):
            e = c/2;
            self.Bc.append(c/360.0)
            self.Cc.append(c/(2 * pi))
            self.zc.append((e,e))
            self.Ac.append(c)
            c *= 2
                
    def fromPixelToLL(self,px,zoom):
         e = self.zc[zoom]
         f = (px[0] - e[0])/self.Bc[zoom]
         g = (px[1] - e[1])/-self.Cc[zoom]
         h = RAD_TO_DEG * ( 2 * atan(exp(g)) - 0.5 * pi)
         return (f,h)


class RenderThread:
    def __init__(self, mapfile, q, tile_dir):
        self.q = q
        self.tile_dir = tile_dir
        self.m = mapnik.Map(256, 256)

        # Load style XML
        mapnik.load_map(self.m, mapfile, True)

        self.prj = mapnik.Projection(self.m.srs)
        self.tileproj = GoogleProjection(zoom+1)

    def render_tile(self, x_y, x, y, z):
        # Calculate pixel positions of bottom-left & top-right
        p0 = (x * 256, (y + 1) * 256)
        p1 = ((x + 1) * 256, y * 256)

        # Convert to LatLong (EPSG:4326)
        l0 = self.tileproj.fromPixelToLL(p0, z);
        l1 = self.tileproj.fromPixelToLL(p1, z);

        # Convert to map projection (e.g. mercator co-ords EPSG:900913)
        c0 = self.prj.forward(mapnik.Coord(l0[0],l0[1]))
        c1 = self.prj.forward(mapnik.Coord(l1[0],l1[1]))

        # Bounding box for the tile
        bbox = mapnik.Box2d(c0.x,c0.y, c1.x,c1.y)

        self.m.resize(256, 256)
        self.m.zoom_to_box(bbox)
        if(self.m.buffer_size < 128):
            self.m.buffer_size = 128

        # Render image with default Agg renderer
        im = mapnik.Image(256, 256)
        mapnik.render(self.m, im)
        tile_uri = self.tile_dir + "/" + x_y + ".png"
        im.save(tile_uri, "png256")

    def loop(self):
        while True:
            #Fetch a tile from the queue and render it
            r = self.q.get()
            if (r == None):
                self.q.task_done()
                break
            else:
                (x_y, x, y, z) = r

            self.render_tile(x_y, x, y, z)
            self.q.task_done()


def render_tiles(mapfile, zoom, path, tile_dir):
    queue = Queue(32)

    renderers = {}
    for i in range(NUM_THREADS):
        renderer = RenderThread(mapfile, queue, tile_dir)
        render_thread = threading.Thread(target=renderer.loop)
        render_thread.start()
        renderers[i] = render_thread

    f = open(path)
    x_y = f.readline().strip()     # 1行を文字列として読み込む
    while x_y:
        items = x_y.split('_')
        x = int(items[0])
        y = int(items[1])
        t = (x_y, x, y, zoom)
        try:
            queue.put(t)
            x_y = f.readline().strip()
        except KeyboardInterrupt:
            print "Ctrl-c detected, exiting..."
            break
    f.close

    # Signal render threads to exit by sending empty request to queue
    for i in range(NUM_THREADS):
        queue.put(None)

    # wait for pending rendering jobs to complete
    queue.join()
    for i in range(NUM_THREADS):
        renderers[i].join()


if __name__ == "__main__":
    mapfile = "c:/mapnik-stylesheets-master/my_osm1.xml"
    zoom = int(sys.argv[1])
    path = sys.argv[2]
    tile_dir = sys.argv[3]

    render_tiles(mapfile, zoom, path, tile_dir)