Dive Into Python3

初めてのPythonプログラム

飛び込む

正確なファイルサイズをバイト単位で受け取って、(正確ではないが)「美しい」表記のサイズを求める関数を定義。

In [1]:
SUFFIXES = {
    1000: ['KB', 'MB', 'GB', 'TB', 'PB', 'EB','ZB','YB'],
    1024: ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']
}

def approximate_size(size:int, a_kilobyte_is_1024_bytes=True)->str:
    '''Convert a file size to human-readable form.

    Keyword arguments:
    size -- file size in bytes
    a_kilobyte_is_1024_bytes -- if True (default), use multiples of 1024
                                if False, use multiples of 1000

    Returns: string

    '''
    if size < 0:
        raise ValueErrror('number must be non-negative');
    
    multiple = 1024 if a_kilobyte_is_1024_bytes else 1000
    
    for suffix in SUFFIXES[multiple]:
        size /= multiple
        if size < multiple:
            return '{0:.1f} {1}'.format(size, suffix)
    
    raise ValueError('number too large')
In [2]:
print(approximate_size(1000))
1.0 KiB
In [3]:
print(approximate_size(10000))
9.8 KiB
In [4]:
print(approximate_size(1000000000000))
931.3 GiB
In [5]:
print(approximate_size(1000000000000, False))
1.0 TB

関数を定義

関数定義にはキーワードdefを使う。

def approximate_size(size, a_kilobyte_is_1024_bytes=True)

動的型言語なので、 戻り値や引数に型はいらない。

実際にはPythonのすべての関数は値を返していて、 関数がreturn文を実行する場合はその値を返し、 実行しない場合はPythonのNull値である None を返す

Pythonでは、変数が明示的に型付けされることは絶対にない。Python が変数の型を判断して内部的に追跡する。

オプション引数と名前付き引数

Pythonでは関数の引数にデフォルト値を持たせることができる。つまり、引数に対応する値無しで関数が呼び出された場合には、その引数にデフォルト値が割り当てられる。

これに加えて、名前付き引数を使うことによって引数を任意の順序で指定することもできる。

結論からいうと、 名前付き引数を使うと引数の順番を考慮しなくていいが、 1度使った場合、それ以降の引数も名前付き引数にしなければならない。

第2引数を名前付き引数で呼んだ場合:

In [6]:
approximate_size(4000, a_kilobyte_is_1024_bytes=False)
Out[6]:
'4.0 KB'

第1第2引数ともに名前付き引数で呼んだ場合:

In [7]:
approximate_size(size=4000, a_kilobyte_is_1024_bytes=False)
Out[7]:
'4.0 KB'

名前付き引数にして順序を入れ替えた場合:

名前付き引数の場合、順序を無視できる。

In [8]:
approximate_size(a_kilobyte_is_1024_bytes=False, size=4000)
Out[8]:
'4.0 KB'

順序を入れ替えて元の第1引数:

引数リストを左から右へ読んだときに名前付き引数が現れたら、それ以降の引数には必ず名前を付けなければならない。

そのため、下のコードはエラーとなる。

In [9]:
approximate_size(a_kilobyte_is_1024_bytes=False, 4000)
  File "<ipython-input-9-b268b6835021>", line 1
    approximate_size(a_kilobyte_is_1024_bytes=False, 4000)
                                                    ^
SyntaxError: positional argument follows keyword argument

順序は守られているが、第1引数のみ名前付き引数の場合:

第2引数が名前付き引数でないため、エラーとなる。

In [10]:
approximate_size(size=4000, False)
  File "<ipython-input-10-ea8ec8e16ab2>", line 1
    approximate_size(size=4000, False)
                               ^
SyntaxError: positional argument follows keyword argument

読みやすいコードを書く

コードの最も重要な読者というのは、コードを書いてから6ヶ月後の(i.e.どこかを修正する必要があるのだけど、コードの中身をすっかり忘れてしまってる)自分自身だということ。

ドキュメンテーション文字列

Pythonの関数にドキュメンテーション文字列(略してdocstringという)を与えることで、その関数のドキュメントを書くことができる。

三重クォートは複数行文字列を表す。開始クォートと終了クォートの間にあるすべてのものが単一の文字列の一部と解釈され、改行文字や行の先頭にある空白、その他のクォート文字も文字列とみなされる。

`def approximate_size(size, a_kilobyte_is_1024_bytes=True):

'''Convert a file size to human-readable form.

Keyword arguments:
size -- file size in bytes
a_kilobyte_is_1024_bytes -- if True (default), use multiples of 1024
                            if False, use multiples of 1000

Returns: string

'''

`

三重クォートは、シングルクォートとダブルクォートの両方を含んだ文字列を定義する簡易な方法でもある。

docstringをもし付けるなら、関数の一番初め(つまり関数宣言の次の行)で定義する。 docstringは実行時に関数の属性として利用できる。

多くのPython用ideは、文脈依存のドキュメントを提供するためにdocstringを使っているので、関数名を入力すると、その関数のdocstringがツールチップとして表示される場合が多い。

すべての関数にきちんとしたdocstringを与えよう。

ちなみに、docstringを確かめるには、 組み込み関数であるhelp関数

In [11]:
help(approximate_size)
Help on function approximate_size in module __main__:

approximate_size(size:int, a_kilobyte_is_1024_bytes=True) -> str
    Convert a file size to human-readable form.
    
    Keyword arguments:
    size -- file size in bytes
    a_kilobyte_is_1024_bytes -- if True (default), use multiples of 1024
                                if False, use multiples of 1000
    
    Returns: string

import検索パス

モジュールをインポートしようとすると、Pythonは何ヶ所かを参照する。 具体的に言うと、Pythonはsys.path に定められたすべてのディレクトリの中を見る。sys.pathは単なるリストなので、標準的なリストのメソッドを使って、簡単にそれを見たり、内容を変更することもできる。

In [12]:
import sys
>sys.path

['',
 'C:\\Miniconda3\\Lib\\site-packages\\ipykernel',
 'C:\\Users\\nabana\\GoogleDrive\\JupyterNote',
 'C:\\Miniconda3\\python35.zip',
 'C:\\Miniconda3\\DLLs',
 'C:\\Miniconda3\\lib',
 'C:\\Miniconda3',
 ...
 ]
In [13]:
sys
Out[13]:
<module 'sys' (built-in)>
In [14]:
help(sys.path.insert)
Help on built-in function insert:

insert(...) method of builtins.list instance
    L.insert(index, object) -- insert object before index

sys.pathにディレクトリ名を追加することによって、実行時に新たなディレクトリをPythonの検索パスに追加できる。これによって、Pythonはモジュールをインポートするときに、そのディレクトリも参照するようになる。この効果はPythonの実行が終了するまで続く。

sys.path.insert(0, 'C:\\Workspace')

sys.path.insert(0, new_path)を使用して、新しいディレクトリをsys.pathリストの先頭に追加した。これで、このディレクトリがPythonの検索パスの一番初めに来ることになる。

万が一、名前の衝突が起きたとしても、こうすれば確実に望みのモジュールが発見され、Python付属のモジュールの代わりに使われることになる。

> sys.path

['C:\\Workspace',

]

あらゆるものがオブジェクト

Pythonの関数は属性を持っており、 それらの属性は実行時に利用できる。 Python では、他のあらゆるものと同様に、関数もオブジェクトになる。

関数を呼び出すのではなく、関数の属性の1つである __doc__ を呼び出している。

In [15]:
print(approximate_size.__doc__)
Convert a file size to human-readable form.

    Keyword arguments:
    size -- file size in bytes
    a_kilobyte_is_1024_bytes -- if True (default), use multiples of 1024
                                if False, use multiples of 1000

    Returns: string

    

Pythonのモジュールをimportすると、モジュールの関数は モジュール.関数でアクセスできる。

オブジェクトとは何か?

Pythonではあらゆるものがオブジェクトであり、すべてのオブジェクトは属性とメソッドを持つことができる。 すべての関数は組み込み属性の __doc__ を持っており、これはその関数のソースコード上に定義されたdocstring を返す。

sysモジュールは(他の属性に混じって)pathという属性を持つオブジェクトになる。

Pythonでのオブジェクトの定義は、 一部のオブジェクトは属性もメソッドも持たないが、持つこともできる。 全てのオブジェクトがサブクラス化できるわけではないが、 それを変数に代入することができ、関数の引数として渡すことができるという意味では、すべてがオブジェクトになる。

Pythonでは、関数はファーストクラスオブジェクトになる。 したがって、関数を他の関数の引数として渡すことができる。

モジュールもファーストクラスオブジェクトになる。 したがって、モジュールをまるごと関数の引数として渡すことができる。

クラスもファーストクラスオブジェクトであり、 クラスの個々のインスタンスもファーストクラスオブジェクトになる。

コードをインデントする

Pythonの関数は、関数コードの開始と終了を示すための明示的なbegin/endや波括弧{}を持たない。 唯一の区切り文字はコロン (:) とコード自体のインデントになる。

コードブロックはインデントによって定められる。 コードブロック という言葉は、 関数、if文、forループ、whileループなどを表している。

インデントでブロックが始まり、インデントの解除でブロックが終わる。

インデントの大きな利点の一つは、すべてのPythonプログラムが同じような体裁を持つようになること。 これはインデントが単なるスタイルではなく、 言語上の要求であることの帰結であり、他人が書いたコードを読んで理解することをより容易にしてくれる。

  • Python

文を分けるために改行を使い、コードブロックを分けるためにコロンとインデントを使う。

  • c++やJava

文を分けるためにセミコロンを使い、コードブロックを分けるのに波括弧を使う。

例外

例外は、Python のあらゆる所にある。 Python の事実上すべての標準モジュールが例外を使用しているし、 Python自体も実に様々な状況において例外を送出する。

例外とは、通常これはエラーであり、何かがうまくいかなかったことを知らせるもの。 一部の言語は、エラーを戻り値として返すことを奨励しており、 一方で、Pythonは、例外の使用を奨励している。

Pythonシェルでエラーが起きたときは、シェルは例外の詳細とそれがどうやって起きたのかを表示し、処理を止めてしまう。 これは「未処理例外」とよばれる。 例外が発生したときに、これを明示的に見つけて処理するコードが1つも存在しない場合は、例外がPythonシェルのトップレベルまで浮き上がってきて、デバッグ情報を吐き出し、それで実行が終了する。

シェルでは、これはたいした問題にはならないが、実際のPythonプログラムが動作している最中にこれが起きた場合には、例外が処理されない限り、プログラムが悲鳴を上げて停止することになる。 これは望ましい動作かもしれないし、そうでない場合もある。

Javaとは違い、Pythonの関数は、自身がどのような例外を送出するのかを宣言しない。 発生しうる例外のうち捕捉すべきものを見定める必要がある。

そのコードが例外を発生させることを知っているのならば、 try...exceptブロックを使って例外を処理できる。

  • Python

例外を処理するためにtry...exceptブロック使い、 例外を生成するためにraise文を使う。

  • Javaやc++

例外を処理するためにtry...catchブロック使い、例外を生成するためにthrow文を使う。

例外を発生させる構文はシンプル。 raise文を使い、その後に例外名と、オプションでデバッグ用の人間が読める文字列を置く。

サンプルコード(approximate_size())の場合、 sizeが0より小さいか、想定より大きい場合に例外が発生するようにしている。

if size < 0:
        raise ValueErrror('number must be non-negative');
raise ValueError('number too large')

例外は、それを発生させた関数の中で処理される必要はない。 もし、その関数が例外を処理しない場合は、例外はその関数を呼び出している関数へ渡され、更にその関数を呼び出している関数に渡され、以降同様に「スタックをさかのぼる」。 もしどの関数でも例外が処理されない場合は、プログラムはクラッシュし、Pythonが "Traceback" というものを標準エラー出力に出力し、一連の流れが終わる。

(Java のprintStackTrace)に似ている。

インポートエラーを補足する

Pythonの組み込み例外の1つに 「ImportError」 があり、 これはモジュールをインポートしようとして失敗したときに発生する。 これは様々な理由で起きうるが、最も単純なケースは、インポートしようとしたモジュールがimport検索パスのなかに見つからない場合が多い。

これを使えば、プログラムにオプション機能を追加できる。 例えば、chardetライブラリは文字コードの自動判定を行う機能を備えている。 プログラムは、このライブラリがもし存在すればこれを使いたいが、ユーザがこれをインストールしていない場合でもそのまま実行を続けたいかもしれない。 このような処理は try...except ブロックを使えば実現できる。

In [16]:
try:
  import chardet
except ImportError:
  chardet = None

#これ以降、chardetモジュールの存在を単純なif文で判断できる。

if chardet:
    print('do something')
else:
    print('continue anyway')
continue anyway

ImportError例外が使われるもう一つの一般的な場面は、 2つのモジュールが共通のapiを実装しているが、どちらか一方が他方より望ましい(高速だったり、メモリ使用が少ないなど)という場合。

この時、まず一方のモジュールをインポートしようとしてみて、それが失敗したときには、もう一方のモジュールへフォールバックできる。

例えば、XMLの章ではElementTree apiと呼ばれる共通のapiを実装した2つのモジュールについて触れることになる。

  • 1つはlxmlというサードパーティのモジュールで、自分でインストールしなければ使えない。
  • 2つ目はxml.etree.ElementTreeというモジュールで、これはPython 3の標準ライブラリに入っているものの処理速度が遅い。
In [17]:
try:
    from lxml import etree
except ImportError:
    import xml.etree.ElementTree as etree # フォールバック
    
etree
Out[17]:
<module 'xml.etree.ElementTree' from 'C:\\Miniconda3\\lib\\xml\\etree\\ElementTree.py'>

このtry...exceptブロックを抜けるころには、何らかのモジュールがインポートされて、それをetreeという名前で呼び出せるようになっている。

両者のモジュールは共通のapiを実装しているので、コードの他の部分では、どちらのモジュールがインポートされたのかを気にする必要はない。 そして、モジュールは常にetreeという名前でインポートされるので、違う名前のモジュールを呼び分けるために、コードのあちこちをif文で汚す必要はない。

未束縛の変数

Pythonが許さないのは、値がまだ代入されていない変数を参照することだ。それをしようとすると、「NameError例外」が発生する。

In [18]:
x
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-18-401b30e3b8b5> in <module>()
----> 1 x

NameError: name 'x' is not defined
In [19]:
x = 2
x
Out[19]:
2

あらゆるものにおいて大文字と小文字が区別される

Pythonにおけるすべての名前( 変数名・関数名・クラス名・モジュール名・例外名)では、 大文字と小文字が区別される。 取得・設定・呼び出し・生成・インポート・送出ができるものなら、大文字と小文字が区別される。

In [20]:
an_integer = 1
an_integer
Out[20]:
1
In [21]:
AN_INTEGER
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-21-f5f3422a1bec> in <module>()
----> 1 AN_INTEGER

NameError: name 'AN_INTEGER' is not defined
In [22]:
An_Integer
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-22-2cbf7094d418> in <module>()
----> 1 An_Integer

NameError: name 'An_Integer' is not defined

スクリプトを実行する

Pythonのモジュールはオブジェクトであり、いくつかの便利な属性を持っている。これを使えば、モジュールを書いたときに、そのモジュールを簡単にテストできるようになる。 Pythonファイルをコマンドライン上で走らせた場合だけに実行される特別なブロックを書き加えればいい。

まず、モジュールはオブジェクトであり、 すべてのモジュールは__name__ という組み込みの属性を持っている。 モジュールの__name__ の値は、そのモジュールをどのように使っているかに応じて決まる。 そのモジュールを import した場合は、__name__ はモジュールのファイル名からディレクトリ名や拡張子を取り除いたものになる。

In [23]:
import sys
sys.__name__
Out[23]:
'sys'

モジュールは、スタンドアローンのプログラムとして直接実行することもできる。 この場合には __name__ は、特別なデフォルト値の"main"になる。 Pythonはこの if 文を評価し、その式が真だということを知り、ifのコードブロックを実行する。この例では、2つの値が表示される。

if __name__ == '__main__':
    print(approximate_size(1000000000000, False))
    print(approximate_size(1000000000000))

C言語と同様に、Pythonは「==」を比較のために使い、 「=」を代入のために使う。 C言語とは違い、Pythonはインラインの代入をサポートしていないので、比較をするつもりで誤って代入をしてしまう可能性はない。

参考リンク

python tutorial にスタイルガイド(コーディング規則)としてPEP8 が紹介されている。

pep8やdocstringについて

*エンコードがutf-8でない。

Pythonプロジェクトをリリースする際に読んでおくとよさげ。テストツールの使い方について書いてある。