Python の基礎

2024年2月3日

Python とは

Python は、C 言語などと違って気軽に使える軽量 (lightweight) 言語の 1 つである。近年は、データ分析や機械学習などに向いた言語として注目を集めている。これは、Python という言語がそれらの仕事に向いているというより、まずその手軽さが注目され、ユーザーが環境を整備していった結果、そういう環境 (エコシステム) が出来上がったということである。

Python は、バージョン 2 から 3 に移行するときに、いくらか後方互換性を犠牲にしている。すなわち、バージョン 2 で書いたコードがバージョン 3 では動かない可能性がある。そのため、現在もバージョン 2 とバージョン 3 が併存している状況にある。ただ、新たにコードを書くのであれば、バージョン 3 で書けばよい。ここではバージョン 3 を想定する。

インストール

Ubuntu (20.04 LTS) の場合だと、次のようにする。まず、レポジトリを追加する。

$ sudo add-apt-repository ppa:deadsnakes/ppa

Python 3.11 をインストールしたければ、次のようにする。

$ sudo apt-get update
$ sudo apt-get install python3.11 python3.11-venv

venv は必須ではないが、あったほうが便利である。

環境整備

Python の強力さは、Python 自身よりもそのエコシステムにある。様々な便利なパッケージ、ツールが開発されていて、それを用いてこそ真価が発揮される。

パッケージを用いるには、パッケージをインストールする必要がある。ただ、パッケージ同士には依存関係があり、適当にインストールしていると環境が壊れたり、わけがわからなくなることがある。そういうときのために、環境をまるごとぽいっと捨てて、新しく作りなおせるようにしておくと便利である。そのための仕組みが仮想環境であり、それを提供するのが venv である。

ベースとなる仮想環境を作る。まず、仮想環境を入れる場所を作る。

$ mkdir ~/venv

"base" という名前の仮想環境を venv で作る。

$ python3.11 -m venv ~/venv/base

activate で環境を有効にする。

$ . ~/venv/base/bin/activate

プロンプトが変わる。

(base) $

この状態は、base 環境の中にいるので、パッケージをインストールすると base 環境の中に入る。環境を切り替えると、そのパッケージは (切り替え先の環境になければ) 使えなくなる。

環境を無効にするには deactivate を実行する。

(base) $ deactivate

デフォルトで base 環境が起動するようにしておけばよい。

~/.bashrc

. ~/venv/base/bin/activate

ただし、仮想環境は (特に指定しなければ) 作成に用いた Python のバージョンになることに注意。パッケージは、同一パッケージでも Python のバージョンごとに用意されているため、細かいバージョンの違い (3.9 とか 3.10 とか) が問題になることがある。

なにか新しいパッケージなりツールなりを試す場合は、新しく仮想環境を作ればよい。

$ python -m venv ~/venv/test
$ . ~/venv/test/bin/activate

仮想環境がいらなくなったら、ふつうに仮想環境のディレクトリ (ここでは "~/venv/test") を削除すればよい。

Python の実行

環境にもよるが、ふつうは python と打てば起動する。

$ python
Python 3.11.4 (main, Jun  7 2023, 12:45:49) [GCC 9.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> 

バージョンが 2 になっていたら、python3 などと打てばバージョン 3 が起動するかもしれない。

そのままコードを実行できる。

>>> a = 1
>>> a + 1
2

終了するには Ctrl-D か quit() あるいは exit() を入力する。

>>> quit()

コードをファイルに記述しておくこともできる (これをスクリプトという)。拡張子は .py が使われる。

hello.py

print("Hello")

次のように実行する。

$ python hello.py
Hello

ヘルプ

Python のコマンドラインではヘルプが使える。

> help(print)

ページャーが開くので、矢印で移動、q で終了する。

式

Python のコードは式や構文で構成される。式の終わりは基本的には行末である。

>>> a = 1 + 2

バックスラッシュで改行を入れることもできる。

>>> a = \
... 1 + \
... 2

1 行で複数の式を書きたい場合は、セミコロンで区切ることができる。

>>> a = 1; b = 2; a + b
3

コメント

# 以降は行末までコメントである。

# hello.py
print("Hello")

以下のような書き方もできる。

"""
hello.py
"""
print("Hello")

この場合、"""...""" に好きなコメントを書ける。これは、コメントというより、文字列を生で置いている感じだが、Python はこれを無視する (この形式は、ドキュメントとして特別な意味を持つことがある)。

変数の定義

名前に値を = で割り当てればよい。これを代入 (assignment) という。

>>> a = 1

変数名は、大文字と小文字が区別される。

これとは別に、式の中で変数に値を代入するための := (セイウチ記号) というものがある。

>>> (a := 2) + 1
3

>>> a
2

これは、条件文で == の代わりにうっかり = を使ってしまうのを避けるためらしい。

定義されている名前を確認するには、dir() を用いる。

>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'a']

名前の先頭にアンダースコア "_" が付いたもの (特に頭とお尻に "__" が付いたもの) は、だいたいは「こっそり定義さているもの」である。これを除いて表示するには、次のようにすればよいかもしれない。

>>> [x for x in dir() if x[0] != "_"]
['a']

変数を削除するには、del を用いる。

>>> del a

Python コマンドラインでは、"_" は特別な変数で、直前の結果を値としてもつ。

>>> 2*3
6

>>> _*3
18

ただし、"_" という名前で新たな変数を定義できる (上書きしてしまう) ので注意。

Python の変数とオブジェクト

Python の場合、変数というのはオブジェクトを入れる箱に名前を付けたものである。名付けは代入によって行われる。これを束縛 (bind) ともいう。C 言語などでは変数の名前と箱は最初の結びつきを保持し続けるが、Python の場合は名前と箱の結びつきが動的に処理される。すなわち、代入操作によって比較的たやすく名付けの対象が切り替わる。

たとえば、次のようなコードを考える。

>>> a = 1
>>> b = a
>>> b
1

"a = 1" は、「a に 1 という値を入れる」ように見えるが、実際は「箱に 1 で表現されるオブジェクトを入れて、それに a と名付ける (結びつける)」という意味である。"b = a" は、「a の値を b に入れる」ようにも見えるが、実際は「a と結びついている箱に b を結びつける」というイメージである。このあと、以下のようにしたとする。

>>> b = 2
>>> a
1

この場合、「b に 2 で表現されるオブジェクトの入った箱を新たに結びつける」と解釈する。a と関連しているオブジェクトは変わらない。

そもそもオブジェクトって何よ、という話だが、オブジェクトとは内部状態をもった実体である。「変数の値」という言い方がされる場合があるが、その場合、変数と結びついた箱に入ったオブジェクトという意味と、そのオブジェクトの内部状態という意味がありえる。これらの区別をしっかりつけておかないと、混乱することになる。公式文書の場合、単に「変数の値」と言った場合は、たぶん前者 (正確にはオブジェクトの参照) を意味している。

実は、数値の場合、オブジェクトの値 (内部状態) を変えられないようになっている。オブジェクトの値を変えられるリストの場合だと、次のようになる。

>>> a = [1, 2, 3]
>>> b = a
>>> b[0] = 2
>>> b
[2, 2, 3]

>>> a
[2, 2, 3]

b = a で a と結びついている箱を b にも結びつけている。したがって、b のオブジェクトが持っている値を変更すると、a のオブジェクトが持っている値も変更される。

Python の代入 = は、変数に直接オブジェクトを指定した場合はそのオブジェクトが変数にセットされる (ように見える) が、変数間の代入の場合は、オブジェクトをコピーせず、オブジェクトが入った箱の情報だけがセットされるのである。平たく言うとオブジェクトの参照が渡されている。唯一無二の、アイデンティティをもった、他でもないそのオブジェクトそのものの情報が渡されていると言ってもよい。

Python の世界では、それぞれのオブジェクトは、たとえ型や内部状態が同じでも、それぞれユニークな存在である。それぞれのオブジェクトは箱に入っているが、箱の間を移動しない。まったく見た目が同じオブジェクト同士でも、箱の場所の違いで区別される。そして変数は、その箱に付けた名前であり、同じ箱に複数の名前を付けることができるし、ある名前を他の箱に付け直すこともできるのである。

※一般的に「変数は値を入れる箱である」という説明がされる場合があるが、その場合、Python の変数という箱に入っている値というのは、オブジェクトそのものではなくその参照 (居場所) であり、変数とは C/C++ でいうところのポインタや参照を意味していることになる。Python と異なる考え方をする言語のひとつが R である。R における名前はオブジェクトそのものの名前であり、名前が違えばオブジェクトそのものが異なる (そもそも変数ではない)。そのため、R において代入は新たなオブジェクトに対する名付けであり、名前間の代入は意味的には常にコピーである (実装上は「即コピー」というわけではないが)。

データ型

Python においては、すべてのデータがオブジェクトであり、データ型はクラスの種類を意味する。データの型は type() でわかる。

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

整数型

整数表現 (小数点なしの数値表現) は、整数型と認識される。

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

浮動小数点数型

実数表現は (整数でなければ) 浮動小数点数とみなされる (たいていの場合は倍精度らしい)。

>>> type(1.2)
<class 'float'>

sys.float_info で最小値と最大値 (絶対値)、マシンイプシロンなどが得られる。

>>> import sys
>>> sys.float_info.min
2.2250738585072014e-308

>>> sys.float_info.max
1.7976931348623157e+308

>>> sys.float_info.epsilon
2.220446049250313e-16

math モジュールでは pi や e が定義されている。

>>> from math import pi, e
>>> pi
3.141592653589793

>>> e
2.718281828459045

明示的な型変換は、"型()" の形で行える。浮動小数点数から整数に変換する場合は、次のようになる。

>>> int(1.2)
1

これは、正確には指定の型のオブジェクトを生成していることになる。

有理数

有理数は fractions モジュールで扱える。

>>> from fractions import Fraction
>>> a = Fraction(2, 3)
>>> a
Fraction(2, 3)

>>> b = a + Fraction(4, 5)
>>> b
Fraction(22, 15)

numerator、denominator で分子、分母をそれぞれ取り出せる。

>>> b.numerator
22

>>> b.denominator
15

浮動小数点数に変換したければ、float() を用いる。

>>> float(b)
1.4666666666666666

複素数型

虚数を表すには j を用いる。

>>> type(1 + 2j)
<class 'complex'>

>>> 1 + 2j
(1+2j)

>>> 1j**2
(-1+0j)

>>> sqrt(-1)
1j

>>> z = 1 + 2j
>>> z.real
1.0

>>> z.imag
2.0

>>> z.conjugate()
(1-2j)

文字列型

"..." あるいは '...' で文字列になる。

>>> a = "Hello"
>>> type(a)
<class 'str'>

"\u" で Unicode の番号を指定できる。

>>> "\u0153"
'œ'

文字列に二重引用符を入れたい場合は、バックスラッシュをつける。

>>> "s = \"xxx\""
's = "xxx"'

あるいは、'...' の中ならそのまま二重引用符を使える。

>>> 's = "xxx"'
's = "xxx"'

長い文字列は、バックスラッシュで改行できる。

>>> "Hello, \
... world."
'Hello, world.'

バックスラッシュはエスケープと呼ばれる特別なものである。上で見た "\u" や改行 "\n" のようなものはエスケープ文字と呼ばれ、特別な文字とみなされる。バックスラッシュ自体を表示したい場合は、"\\" とする。

>> print("\n")


>>> print("\\n")
\n

あるいは、r"..." という形にすると、ナマ (raw) の文字列になり、エスケープされない。

>>> print(r"\n")
\n

各文字のアクセス

文字列の各文字にアクセスするには、添字を使う (0 から始まる)。

>>> s = "Hello, world."
>>> s[0]
'H'

各文字は変更できない。

添字には負の値が使えて、最後から何番目の意味になる。

>>> s[-1]
'.'

>>> s[-2]
'd'

部分文字列にアクセスすることもできる。1:4 なら 2 文字目から 4 文字目になる (1:4 は添字としては 1, 2, 3 を意味する)。これをスライス表記という。

>>> s[1:4]
'ell'

文字列の長さ

文字列の長さを知るには len() を用いる。

>>> len(s)
13

文字列の連結

文字列の連結には + を用いる。

>>> "Hello" + ", " + "world" + "."
'Hello, world.'

リテラル表現 (データそのものの表現) 同士では演算子なしでもくっつく。

>>> "Hello" ", " "world" "."
'Hello, world.'

ただし、変数同士では使えない。

変数の埋め込み

f"..." の中で "{...}" の形で変数を埋め込むことができる。

>>> greet = "Hello"
>>> whom = "world"
>>> f"{greet}, {whom}."
'Hello, world.'

数値でもよい。

>>> a = 1
>>> f"a = {a}"
'a = 1'

計算もできる。

>>> f"a = {2*a}"
'a = 2'

三重引用符

三重引用符 """...""" で改行を入れることができる。

>>> s = """\
... Hello,
... world.
... """
>>> s
'Hello,\nworld.\n'
>>> print(s)
Hello,
world.

三重引用符の中では、二重引用符をエスケープなしで使える。

>>> """\
... s = "Hello, world."
... """
's = "Hello, world."\n'

論理型

False と True を取る bool 型がある。

>>> b = True
>>> type(b)
<class 'bool'>

False、True はそれぞれ 0、1 と同じ意味である。

>>> b + 1
2

列挙型

列挙型は enum モジュールによって定義できる。

>>> from enum import Enum
>>> class CatType(Enum):
...     SABATORA = 1
...     CHATORA = 2
...     MIKE = 3
... 
>>> a = CatType.SABATORA
>>> a
<CatType.SABATORA: 1>

設定する数値は auto() で自動的に割り振ることもできる。

>>> from enum import Enum, auto
>>> class CatType(Enum):
...     SABATORA = auto()
...     CHATORA = auto()
...     MIKE = auto()
... 
>>> a = CatType.CHATORA
>>> a
<CatType.CHATORA: 2>

以下のような定義の仕方もある。

>> CatType = Enum("CatType", ["SABATORA", "CHATORA", "MIKE"])
>>> a = CatType.MIKE
>>> a
<CatType.MIKE: 3>

タプル

(1, 2) のような形で、データをひとまりにしたタプルを作れる。括弧は省略できる場合がある。

>>> x = (1, 2)
>>> x
(1, 2)

>>> x = 1, 2
>>> x
(1, 2)

>>> type(x)
<class 'tuple'>

各要素にアクセスするには、添字を使う (0 から始まる)。

>>> x[0]
1

>>> x[1]
2

要素の値は変更できない。

タプルを使って複数の変数を一度に定義できる。

>>> a, b = 1, 2
>>> a
1

>>> b
2

変数を値を入れ替えたりもできる。

>>> a, b = b, a
>>> a
2

>>> b
1

値が 1 つのタプルがほしい場合は、次のようにする。

>>> (1,)
(1,)

名前付きタプル

名前付きタプルというものがある。collections モジュールで使える。

>>> from collections import namedtuple
>>> X = namedtuple("X", "a b")
>>> x = X(1, 2)
>>> x
X(a=1, b=2)

>>> x.a
1

リスト

次のような形でリストを定義できる。

>>> a = [1, 2, 3]
>>> a
[1, 2, 3]

>>> type(a)
<class 'list'>

リストの要素は型が混在してもよい。

>>> [1, "2"]
[1, '2']

内包表記もできる。

>>> a = [x for x in range(1, 4)]
>>> a
[1, 2, 3]

>>> a = [x for x in range(1, 11) if x % 2 == 0]
>>> a
[2, 4, 6, 8, 10]

続きの数値なら次のような書き方もできる。

>>> list(range(1, 11))
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

range() は範囲で、range(1, 11) だと 1 から 10 までの数値を生成する。ステップ指定もできる。

>>> list(range(1, 11, 2))
[1, 3, 5, 7, 9]

繰り返しを行いたい場合は、繰り返し数をかければよい。

>>> [1, 2]*3
[1, 2, 1, 2, 1, 2]

空の数値リストがほしければ、次のようにできる。

>>> [0]*10
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

要素のアクセス

各要素にアクセスするには、添字を使う (0 から始まる)。

>>> a[0]
1

0:2 の形で 1 要素目から 2 要素目までの部分リストを取り出せる。

>>> a[0:2]
[1, 2]

リストの長さ

長さを知りたければ len() を使う。

>>> len(a)
3

要素の追加や取り出し

append() で末尾に要素を追加できる。

>>> a.append(4)
>>> a
[1, 2, 3, 4]

逆に末尾の要素を取り出すには pop() を使う。

>>> a.pop()
4

>>> a
[1, 2, 3]

途中に要素を挿入するには insert() を用いる。

>>> a.insert(1, 5)
>>> a
[1, 5, 2, 3]

pop() に添字を渡すと、任意の要素を取り出せる。

>>> a.pop(1)
5

>>> a
[1, 2, 3]

リストの連結

末尾にリストを追加したい場合は、+ で足せばよい。

>>> a += [5, 6, 4]
>>> a
[1, 2, 3, 5, 6, 4]

リストの代入

リストの要素を先頭とそれ以外に分けて変数に入れるには、次のようにできる。

>>> a, *b = [1, 2, 3]
>>> a
1

>>> b
[2, 3]

リストを別の変数に代入する場合には注意が必要である。

>>> a = [1, 2, 3]
>>> b = a
>>> b[0] = 2
>>> b
[2, 2, 3]

>>> a
[2, 2, 3]

上の例では、b の要素の値を変更すると、a の要素の値も変わってしまう。代入はオブジェクトを入れる箱に名前を付けるだけのため、b は a と同じオブジェクトを見ているからである。b に a のコピーを割り当てたい場合は、copy() を用いる。

>>> a = [1, 2, 3]
>>> b = a.copy()
>>> b[0] = 2
>>> b
[2, 2, 3]

>>> a
[1, 2, 3]

入れ子のリスト

リストは入れ子にできる (リストをリストの要素にできる)。

>>> a = [[1, 2], [3, 4]]
>>> a[0]
[1, 2]

>>> a[0][0]
1

集合

リストに少し似たものとして集合 (set) がある。

>>> a = {1, 2, 2, 0, 5}
>>> a
{0, 1, 2, 5}

>>> type(a)
<class 'set'>

集合では値は一意で、順番は関係ない。

リストから集合を作ることもできる。

>>> a = set([1, 2, 2, 0, 5])
>>> a
{0, 1, 2, 5}

要素を追加・削除するにはそれぞれ add()、remove() を用いる。

>>> a.add(7)
>>> a
{0, 1, 2, 5, 7}

>>> a.remove(7)
>>> a
{0, 1, 2, 5}

合併は union() で得られる。

>>> b = {0, 4, 7, 3}
>>> a.union(b)
{0, 1, 2, 3, 4, 5, 7}

共通部分は intersction() で得られる。

>>> a.intersection(b)
{0}

集合をリストにしたい場合は、list() を用いる。

>>> list(a.union(b))
[0, 1, 2, 3, 4, 5, 7]

空の集合がほしい場合は、set() を用いる。{} だと空の辞書になるため。

>>> a = set()
>>> a
set()

>>> a.add(1)
>>> a.add(2)
>>> a.add(3)
>>> a
{1, 2, 3}

辞書

キーに対して値を持つ辞書 (連想配列) を作ることができる。

>>> a = {"A": 1, "B": 2}
>>> a
{'A': 1, 'B': 2}

>>> a["B"]
2

>>> type(a)
<class 'dict'>

次のような定義のしかたもある。

>>> a = dict(A=1, B=2)
>>> a
{'A': 1, 'B': 2}

>>> a = dict([("A", 1), ("B", 2)])
>>> a
{'A': 1, 'B': 2}

空の辞書を定義してキーと値を追加していくこともできる。

>>> a = {}
>>> a["A"] = 1
>>> a["B"] = 2
>>> a
{'A': 1, 'B': 2}

キーを持っているかどうかは次のように調べられる。

>>> "A" in a.keys()
True

クラス

クラスは、複数の変数や関数をひとまとめにしたものである。クラスの実体がオブジェクトであり、クラスは要するにデータ型のことである。クラスは自分で定義することができる。

>>> class Prof:
...     def __init__(self, name, type, age):
...             self.name = name
...             self.type = type
...             self.age = age
... 
>>> prof = Prof("omochi", "sabatora", 4)
>>> prof.name
'omochi'

>>> type(prof)
<class '__main__.Prof'>

__init__() はコンストラクタで、オブジェクトを作るための関数であり、Prof() の形で呼び出される。self はオブジェクト自身を指し、self.name などはオブジェクトの属性という。

上の例のように、実質的には属性しか持たないクラス (要するに構造体のようなもの) であれば、dataclass を用いてもう少し簡単に定義できる。

>>> from dataclasses import dataclass
>>> @dataclass
... class Prof:
...     name: str
...     type: str
...     age: int
... 
>>> prof = Prof("omochi", "sabatora", 4)
>>> prof.name
'omochi'

ここで、@dataclass はデコーレータというもので、関数に変更を加えるためのものである。ここでは属性にデータ型を指定しているが、これはアノテーションというもので、補足説明のようなものだが、ここでは必要である。

変数から変数への代入ではオブジェクトのコピーを行わないが、自分で定義したクラスのオブジェクトも同じである。コピーしたい場合は、copy モジュールの copy() を用いる。

>>> from copy import copy
>>> prof2 = copy(prof)
>>> prof2.age = 5
>>> prof.age
4

ただし、これは浅いコピー (shallow copy) という。「浅い」の意味は、属性に対してリストなどの値が変更可能なオブジェクトを設定してみればわかる。

>>> prof.food = ["karikari"]
>>> prof2 = copy(prof)
>>> prof2.food.append("nekogusa")
>>> prof.food
['karikari', 'nekogusa']

コピーしたはずなのに、もとのオブジェクトの属性の (オブジェクトの) 値が変わってしまっている。属性のオブジェクトがコピーされていないからである。代わりに「深い」コピーを行う deepcopy() がある。

>>> from copy import deepcopy
>>> prof.food = ["karikari"]
>>> prof2 = deepcopy(prof)
>>> prof2.food.append("nekogusa")
>>> prof.food
['karikari']

これならもとのオブジェクトの属性の (オブジェクトの) 値は変わらない。

式と演算子

式はデータと演算子で構成される。

整数、浮動小数点数については、以下のような演算子が使える。

演算子例意味
+a + b加算
-a - b減算
*a * b乗算
/a / b除算 (浮動小数点数)
//a // b除算 (整数)
**a ** b累乗
%a % b剰余
--a負符号

i += 1 で i = i + 1 を意味するような代入演算子というものもある。また、& や << などのビット演算子というものもある。

制御構造

Python のブロックは、キーワードや記号で囲むような形ではなく、字下げで表現する (オフサイドルールというらしい)。

ブロックは空でよいのに仕様上なにか必要、というときのために pass というものが用意されている。

>>> class Test:
...     pass
...

if...else 構文

if...else 文で条件分岐を作れる。

>>> v = 1
>>> if v == 1:
...     print("sabatora")
... elif v == 2:
...     print("chatora")
... else:
...     print("mike")
... 
sabatora

実行する文が 1 つしかないなら、コロンのあとに書いてもよい。

>>> if v == 1: print("sabatora")
... elif v == 2: print("chatora")
... else: print("mike")
... 
sabatora

値を分岐させることもできる。

>>> i = 0 if v == 1 else 1
>>> i
0

上の例は、少しわかりにくい気がするが、v の値が 1 なら 0 を入れる、さもなければ 1 を入れる、という意味である。

条件文では、以下のような比較演算子や論理演算子が使える。

比較演算子

値を比較する演算子として、<、>、<=、>= がある。"等しい" を表すものは == である (= と間違いやすい)。"等しくない" は != である。

リストや文字列の比較も行える。

>>> [1, 2, 3] == [4, 5, 6]
False

>>> "abc" < "abd"
True

比較演算子はつなげることができる。

>>> x = 0.2
>>> 0 <= x <= 1
True

オプジェクトの同一性をチェックするために is、is not がある。

>>> a = [1, 2, 3]
>>> b = a
>>> a is b
True

>>> c = [1, 2, 3]
>>> a is c
False

>>> a is not c
True

b は代入で a と同じオブジェクトを指しているので、a is b は True だが、c は a と同じ値を持つリストだがオブジェクトとしては異なるため、a is c は False (a is not c は True) になる。

Python には特殊な値として None というものがあり、これをチェックするのにも is が使われる。== を使わない理由は、== はオブジェクトの値を比較するのであって、None は値というよりひとつの (唯一の) オブジェクトなので、それを指しているかどうかが問題だからである。また、== は動作を上書きできるという別の問題もある。

None は何に使うのかというと、たとえばリストを処理しているとき、変数のリストが空だったとすると、問題が起こって空なのか、単純に空なのか、それだけではわからない。そこで、問題が起こったときに変数の値を None とすることで、問題が起こったことを明確にできる。

if x is None:
    # 問題が起こった
    ...
elif x:
    # 通常の処理
    ...
else:
    # リストが空の場合の処理
    ...

リストが空の場合は False と解釈されるため、上のように書ける。

論理演算子

条件を複数記述するための論理演算子として、and、or がある。また、式の頭に not をつけると否定になる。途中で結果が決まったらその先は評価しない (短絡という)。たとえば、and で早々に False になったり、or で早々に True になったりすると、その先は評価されない。関数を呼んでいたりすると、呼ばれないことになる。

たとえば、x が 0 以上でかつ y が 5 以下であるときになにかしたい、という場合は、次のようになる。

if x >= 0 and y <= 5:
    ...

この場合、そもそも x が 0 より小さかったら、y の式はまったく評価されない。

match-case 文

match-case 文は、if 文の特殊なものである。

>>> match v:
...     case 1:
...             print("sabatora")
...     case 2:
...             print("chatora")
...     case 3:
...             print("mike")
...     case _:
...             print("none")
... 
sabatora

for ループ

for ループは規定の回数だけ繰り返したりするときに使う。

>>> for i in range(5):
...     print(i)
... 
0
1
2
3
4

range() は、数値を 1 つだけ指定した場合、0 から始まる。1 から始めたい場合は、次のようにする。

>>> for i in range(1, 6):
...     print(i)
... 
1
2
3
4
5

4 から 0 までループしたい場合は、次のようにする。

>>> for i in range(4, -1, -1):
...     print(i)
... 
4
3
2
1
0

range() の 3 番目の引数はステップで、-1 を足していくという意味である。次のようにしたほうが簡単かもしれない。

>>> for i in reversed(range(5)):
...     print(i)
... 
4
3
2
1
0

リストの要素をループさせることもできる。

>>> for i in ["sabatora", "chatora", "mike"]:
...     print(i)
... 
sabatora
chatora
mike

enumerate() を使えば、添字と値のペアをループさせることもできる。

>>> for i, v in enumerate(["sabatora", "chatora", "mike"]):
...     print(f"{i} {v}")
... 
0 sabatora
1 chatora
2 mike

配列の値のペア (タプル) がほしいのであれば、zip() で得られる。

>>> for i, j in zip(range(3), ["sabatora", "chatora", "mike"]):
...     print((i, j))
... 
(0, 'sabatora')
(1, 'chatora')
(2, 'mike')

for ループには else 節をつけることができる。これは、break などで抜けなかった場合に実行される。たとえば、何かを見つけてループを抜けるという処理をしたいとき、目的のものが見つからなかった場合などに使う。

>>> for i in range(5):
...     print(i)
...     if i == 3: break
... else:
...     print("else")
... 
0
1
2
3

>>> for i in range(5):
...     print(i)
... else:
...     print("else")
... 
0
1
2
3
4
else

範囲型

ここで使われている range() は、範囲型である。

>>> type(range(3))
<class 'range'>

>>> range(3)
range(0, 3)

これは、Python 2 ではリストを返していたが、Python 3 ではジェネレータという、データを 1 つずつ返してくる特殊な機構を用いている。

イテレータというもので、ちまちまデータをもらうこともできる。

>>> i = iter(range(3))
>>> next(i)
0

>>> next(i)
1

>>> next(i)
2

>>> next(i)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

リストがほしければ、list() で変換してやればよい。

>>> list(range(3))
[0, 1, 2]

ジェネレータ式

1 から 5 のそれぞれを 2 乗したものの総和と取りたいとしよう。内包表記でリストを定義して、その総和を取ればよいかもしれない。

>>> [i*i for i in range(1, 6)]
[1, 4, 9, 16, 25]

>>> sum([i*i for i in range(1, 6)])
55

この場合、角括弧を外すことができる。

>>> sum(i*i for i in range(1, 6))
55

これをジェネレータ式といい、range() が返すようなものと同様のものを返す。sum() がリストだけでなく range() のようなものを受け取れるために機能する。実際、ジェネレータ式からイテレータを取得できる。

>>> i = iter(i*i for i in range(1, 6))
>>> next(i)
1

>>> next(i)
4

>>> next(i)
9

>>> next(i)
16

>>> next(i)
25

>>> next(i)
Traceback (most recent call last):
  File "", line 1, in 
StopIteration

スコープ

for ループは特に独自のスコープは持っていないので、その中で定義された変数は外でも使える。

>>> for i in range(5):
...     a = i + 1
...     print(a)
... 
1
2
3
4
5

>>> i
4

>>> a
5

while ループ

while ループは条件が成り立つあいだ繰り返す。

>>> i = 1
>>> while i <= 5:
...     print(i)
...     i += 1
... 
1
2
3
4
5

continue と break

continue でループを次に進めることができる。

>>> for i in range(10):
...     if i % 2 == 0:
...             continue
...     print(i)
... 
1
3
5
7
9

break でループを抜けることができる。

>>> for i in range(10):
...     if i == 5:
...             break
...     print(i)
... 
0
1
2
3
4

例外処理

関数に変な値を渡すとエラーになる。

>>> from math import sqrt
>>> sqrt("a")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: must be real number, not str

これをユーザー側で受け止めることもできる。

>>> try:
...     sqrt("a")
... except:
...     print("Error")
... 
Error

try...except には else 節をつけることができる。これは、例外が起こらなかった場合に実行される。

>>> try:
...     x = sqrt(2)
... except:
...     print("Error")
... else:
...     print(x)
... 
1.4142135623730951

finally 節もある。この節は、例外が起ころうが起こるまいが実行される。

例外を自分で投げる場合は raise を用いる。

>>> raise Exception("Error")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
Exception: Error

例外を捕まえて再送すると、次のようになる。

>>> try:
...     raise Exception("Error")
... except:
...     raise Exception("Error2")
... 
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
Exception: Error

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
Exception: Error2

再送していることを明確にするには、from を用いる。

>>> try:
...     raise Exception("Error")
... except Exception as e:
...     raise Exception("Error2") from e
... 
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
Exception: Error

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
Exception: Error2

再送の連鎖を切りたい場合は、from None を指定すればよい。

>>> try:
...     raise Exception("Error")
... except:
...     raise Exception("Error2") from None
... 
Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
Exception: Error2

受け止める例外を指定することもできる。

>>> try:
...     raise Exception ("Error")
... except Exception as e:
...     print (e)
Error

例外を多重ループを抜けるのに使うことができる。

>>> try:
...     for i in range(5):
...             for j in range(5):
...                     if i == 3: raise StopIteration
...                     print(f"{i} {j}")
... except StopIteration:
...     pass
... 
0 0
0 1
0 2
0 3
0 4
1 0
1 1
1 2
1 3
1 4
2 0
2 1
2 2
2 3
2 4

自分で例外を定義するには、Exception を継承すればよい。

>>> class MyException(Exception):
...     pass
... 

プログラムの停止

プログラムを止めるには、exit() を用いる。

>>> exit(2)
$ echo $?
2

引数に終了コードを指定できる。引数を省略すれば 0 を指定したことになる。

関数

関数は、引数を受け取って値を返す処理のまとまりである。次のように定義する。

>>> def f(x, y):
...     return x + y
... 
>>> f(1, 2)
3

複数の値を返したければ、タプルを使う。

>>> def f(x, y):
...     return x + y, x - y
... 
>>> f(1, 2)
(3, -1)

関数は何も返さなくてもよい。

>>> def f(x):
...     x[0] = 0
... 

これは None を返しているのと同じである。

>>> x = [1, 2, 3]
>>> print(f(x))
None

関数を変数に入れることもできる。

>>> def f(x, y):
...     return x + y
... 
>>> g = f
>>> g(1, 2)
3

Python の関数は、同じ名前で引数が異なる関数を多重定義することはできない。

引数

関数内で引数に値を代入しても、呼び出し元の変数の値は変わらない。

>>> def f(x):
...     x = 0
... 
>>> x = 1
>>> f(x)
>>> x
1

ただし、引数のオブジェクトの値 (内部状態) を変更した場合、呼び出し元のオブジェクトの値も変わってしまう。

>>> def f(x):
...     x[0] = 0
... 
>>> x = [1, 2, 3]
>>> f(x)
>>> x
[0, 2, 3]

つまり、Python の変数の値は正確にはオブジェクトの参照なので、Python における関数は値渡し (call by value) である。ただ「変数の値」という言い方は混乱を招くので、オブジェクト参照渡し (call by object reference) と考えておくのが無難である。

キーワード引数

関数呼び出しの際、引数の名前に対して値を指定することができる。

>>> def f(x, y):
...     return x - y
... 
>>> f(3, 2)
1
>>> f(2, 3)
-1
>>> f(y=2, x=3)
1

辞書を使う方法もある。

>>> f(**{"y": 2, "x": 3})
1

位置で決まる引数とキーワード引数を混在することができるが、キーワード引数は位置で決まる引数のあとに置かなければならない。

>>> f(3, y=2)
1
# f(y=2, 3) だと怒られる

引数を * で区切ると、その前は位置による引数でもキーワード引数でもよい引数、そのあとはキーワード引数に設定できる。

>>> def f(x, *, y):
...     return x - y
... 
>>> f(3, y=2)
1
>>> f(x=3, y=2)
1
# f(3, 2) だと怒られる

さらに引数を / で区切ると、その前は位置による引数、そのあとは位置による引数でもキーワード引数でもよい引数、さらに * のあとはキーワード引数、に設定できる。

>>> def f(x, /, y, *, z):
...     return x + y + z
... 
>>> f(1, 2, z=3)
6
>>> f(1, y=2, z=3)
6
# f(1, 2, 3) や f(x=1, y=2, z=3) だと怒られる

デフォルト値

引数にデフォルト値を設定することができる。デフォルト値がある引数は値を指定してもしなくてもよい。ただし、位置による引数を指定する場合、省略できるのは後ろの引数だけである。キーワード引数を使えば、デフォルト値をもつ任意の引数を省略できる。

>>> def f(x=3, y=2):
...     return x - y
... 
>>> f()
1
>>> f(4)
2
>>> f(6, 3)
3
>>> f(y=0)
3

Python の引数のデフォルト値は、すべての呼び出しで共有される。

>>> def f(x, a=[]):
...    a.append(x)
...    return a
...
>>> f(1)
[1]

>>> f(2)
[1, 2]

>>> f(3)
[1, 2, 3]

可変長引数

可変長引数にするには *args の形にする。

>>> def f(*args):
...     return sum(args)
... 
>>> f(1, 2, 3)
6

*args は引数リストの最後に位置する必要がある。ただし、*args のあとはキーワード引数としてなら利用できる。

>>> def f(*args, op=sum):
...     return op(args)
... 
>>> f(1, 2, 3)
6

>>> from math import prod
>>> f(1, 2, 3, 4, op=prod)
24

この * は、リストの要素を引数に展開するのにも使える。

>>> f(*[1, 2, 3])
6

**args を用いると、任意のキーワード引数を辞書で受け取ることができる。

>>> def f(**args):
...     print(args)
... 
>>> f(a=1, b=2)
{'a': 1, 'b': 2}

** は辞書の要素をキーワード引数に展開するのにも使える。

>>> def f(x, y):
...     return x + y
... 
>>> f(**{"x": 1, "y": 2})
3

再帰呼び出し

関数の再帰呼び出しはふつうにできる。

def fibonacci(n):
    if n == 0: return 0
    elif n == 1: return 1
    else: return fibonacci(n - 2) + fibonacci(n - 1)

無名関数

無名関数は lambda を用いて定義できる。ただし、1 行 (1 つの式) で定義できるものに限る。

>>> f = lambda x, y: x + y
>>> f(1, 2)
3

なぜ複数の文からなる任意の関数を定義できないかというと、おそらくオフサイドルールが関係していると思われる。複数文を書くのは無理だが、タプルを使って無理やり複数式を書くことはできる。

>>> f = lambda x: (
...     a := -x if x < 0 else x,
...     2*a
... )[-1]
>>> f(1)
2
>>> f(-1)
2

クロージャ

次のように、呼ぶたびに 1 増えた数値を返す関数を定義したい。

>>> f()
1

>>> f()
2

>>> f()
3

そのためには、ローカルの環境にオブジェクトを閉じ込める形で関数を定義すればよい。この仕組みをクロージャという。目的の関数は、次のように実現できる。

>>> def setup():
...     x = 0
...     def f():
...             nonlocal x
...             x += 1
...             return x
...     return f
... 
>>> f = setup()
>>> f()
1

>>> f()
2

>>> f()
3

まず、Python では関数をどこでも定義でき、入れ子にもできる。また、関数を値として返すこともできる。内側の関数 f() では、nonlocal というものを用いているが、f() 内の変数ではないけど global 変数でもないよ、という意味である。つまり、ここでは setup() 内にある x を見に行く。

次のようにしてもよい。

>>> def setup(x):
...     def f():
...             nonlocal x
...             x += 1
...             return x
...     return f
... 
>>> f = setup(0)
>>> f()
1

>>> f()
2

>>> f()
3

おまけだが、関数もオブジェクトなので、静的属性を用いてカウンタを定義できる。

>>> def f():
...    f.c += 1
...    return f.c
...
>>> f.c = 0
>>> f()
1
>>> f()
2
>>> f()
3

クラス

C++ などのオブジェクト指向プログラミング対応言語では、データと手続きをひとまとまりとして、オブジェクトの設計図であるクラスを構成することができる。Python もこのタイプのクラスの仕組みがある。

class Prof:
    def __init__(self, name, type, age):
        self.set_name(name)
        self.set_type(type)
        self.set_age(age)

    def set_name(self, name):
        self.name = name

    def set_type(self, type):
        self.type = type

    def set_age(self, age):
        self.age = age

    def show_profile(self):
        print(f"name: {self.name}")
        print(f"type: {self.type}")
        print(f"age: {self.age}")

クラスの手続き (メソッド) を関数と同じように定義している。ただし、第 1 引数は self としている。これはオブジェクト自身を表す。__init__() については、オブジェクト作成時に自動的に呼ばれるコンストラクタである。self.name などはオブジェクトの属性であり、適当に関数内で値を割り当てればよい。Python のクラスには、他の言語にあるようなアクセス制御はないので、運用で気をつけるしかない。

オブジェクトを作るには、次のようにする。

prof1 = Prof("omochi", "sabatora", 3)

オブジェクトのメソッドは、次のように実行できる。

prof1.show_profile()

実行結果

name: omochi
type: sabatora
age: 3

既存のクラスをベースにして別のクラスを作ることができる。これを継承という。

class Prof2(Prof):
    def __init__(self, name, type, age):
        super().__init__(name, type, age)
        self.food = []

    def add_food(self, food):
        self.food.append(food)

    def show_profile(self):
        super().show_profile()
        print(f"food: {self.food}")

クラス名 Prof2 のあとにベースとなるクラスを指定している。ベースのクラスをスーパークラスという。継承で作られるクラスをサブクラスという。サブクラスからスーパークラスにアクセスするには、super() を使う。

prof2 = Prof2("annko", "chatora", 4)
prof2.add_food("karikari")
prof2.show_profile()

実行結果

name: annko
type: chatora
age: 4
food: ['karikari']

ところで、リストの要素にはどんな型でも入れられるが、すべての要素が show_profile() というメソッドをもつと想定して実行させることができる。

class Prof3:
    def show_profile(self):
        print("name: tamago")
        print("type: mike")
        print("age: 5")

prof_list = [prof1, prof2, Prof3()]
for p in prof_list:
    p.show_profile()
    print()

実行結果

name: omochi
type: sabatora
age: 3

name: annko
type: chatora
age: 4
food: ['karikari']

name: tamago
type: mike
age: 5

これは、C++ だと多相性 (仮想関数) を用いて実現するもので、実際 Prof と Prof2 は親子だからよいのだが、Prof3 にいたっては赤の他人であり、Prof の親戚のように振る舞っているだけである。こういう動作を、「アヒルのように振る舞うならそれはアヒルだ」ということわざ?からダックタイピングという。

モジュール

Python では別ファイルにあるものを import で取り込める。この別ファイルになっているものをモジュールという。たとえば、math モジュールをインポートするなら、次のようになる。

>>> import math

この場合、モジュールに属する関数 sqrt() を呼ぶには、math.sqrt() とする。

>>> math.sqrt(2)
1.4142135623730951

モジュール名をいちいち付けるのがめんどくさいことがある。その場合、モジュール名を短くするという方法がある。

>>> import math as m
>>> m.sqrt(2)

あるいは、必要なものだけ選択的に取り込むことができる。

>>> from math import sqrt
>>> sqrt(2)

全部まとめて取り込むこともでき、その場合はモジュール名を付ける必要がなくなる。

>>> from math import *

ただし、既存のものと名前がかぶるときがあり、その場合は上書きされてややこしいことになる。モジュールは名前空間 (名前がかぶってもいいように、名前の有効な範囲を区切る枠) としての役割があるため、上記の方法は推奨されない。めんどくさくてもモジュール名を付けてアクセスするか、必要なものだけ選択的に取り込むのが推奨。

モジュールがどういう名前を定義しているかは、dir() で調べられる。

>>> import math
>>> dir(math)
['__doc__', '__loader__', '__name__', '__package__', '__spec__', 'acos', 'acosh', 'asin', 'asinh', 'atan', 'atan2', 'atanh', 'cbrt', 'ceil', 'comb', 'copysign', 'cos', 'cosh', 'degrees', 'dist', 'e', 'erf', 'erfc', 'exp', 'exp2', 'expm1', 'fabs', 'factorial', 'floor', 'fmod', 'frexp', 'fsum', 'gamma', 'gcd', 'hypot', 'inf', 'isclose', 'isfinite', 'isinf', 'isnan', 'isqrt', 'lcm', 'ldexp', 'lgamma', 'log', 'log10', 'log1p', 'log2', 'modf', 'nan', 'nextafter', 'perm', 'pi', 'pow', 'prod', 'radians', 'remainder', 'sin', 'sinh', 'sqrt', 'tan', 'tanh', 'tau', 'trunc', 'ulp']

モジュールのインポートは何度実行しても実際には一度だけしか実行されない。リロードしたければ、次のようにする。

>>> from importlib import reload
>>> reload(math)

モジュールの場所

モジュールを検索する場所は、sys.path にある場所である。これは、環境変数 PYTHONPATH で追加することができる。

また、カレントディレクトリも検索される。たとえば、"fibonacci.py" というファイルがあるとすると、次のように import で取り込める。

>>> import fibonacci

パッケージ

モジュールは、ディレクトリ階層によっても作ることができ、モジュール下のモジュール (サブモジュール) も作ることができる。こういう形でモジュール群をまとめたものをパッケージという。

たとえば、pkg というパッケージを作ることを考える。その場合、pkg というディレクトリを作り、中に __init__.py というファイルを置く。

pkg/
 |- __init__.py

もし、pkg.f() という関数を提供したければ、__init__.py に直接書けばよい。

def f():
    print("f()")

pkg のあるところで、import すれば使える。

>>> import pkg
>>> pkg.f()
f()

実装ファイルを分けたければ、たとえば _imp.py というファイルを用意して、そちらに関数を書く。そして、__init__.py で読み込めばよい。

from ._imp import *

サブモジュールを作る場合は、さらにディレクトリを作って、やはり __init__.py を用意すればよい。

pkg/
 |- __init__.py
 |- smod/
     |- __init__.py

サブモジュールをインポートする場合は、次のように直接指定すればよい。

>>> import pkg.smod

パッケージのインストール

最初のほうで述べたが、Python の強みはさまざまなパッケージによって構成されたエコシステムである。pip というコマンドを用いると、インターネット上 (PyPI) で公開されたパッケージをインストールすることができる。

venv が使えるとして、テスト用の仮想環境を作る。

(base) $ python -m venv ~/venv/test
(base) $ . ~/venv/test/bin/activate
(test) $

まず、パッケージの検索のために pip-search をインストールしてみる。

(test) $ pip install pip-search

これでパッケージを検索できる。たとえば、NumPy が使ってみたい場合は、次のように検索できる。

(test) $ pip_search NumPy

どれが目的のパッケージか、注意深く確認する (怪しいものを紛れ込ませるヤツがいるので)。パッケージの公式サイトがあるなら、そちらのインストール方法を見たほうが確実かもしれない。ここでは "numpy" が正解なので、以下のようにインストールする。

(test) $ pip install numpy

インポートしてみて成功したら OK。

>>> import numpy

たまに、パッケージのバージョンを指定したい場合がある。その場合、以下のようにするとよい。

(test) $ pip install "numpy=="

そうするとエラーになるが、指定可能なバージョンを教えてくれる。

ERROR: Could not find a version that satisfies the requirement numpy== (from versions: 1.3.0, 1.4.1, 1.5.0, 1.5.1, 1.6.0, 1.6.1, 1.6.2, 1.7.0, 1.7.1, 1.7.2, 1.8.0, 1.8.1, 1.8.2, 1.9.0, 1.9.1, 1.9.2, 1.9.3, 1.10.0.post2, 1.10.1, 1.10.2, 1.10.4, 1.11.0, 1.11.1, 1.11.2, 1.11.3, 1.12.0, 1.12.1, 1.13.0, 1.13.1, 1.13.3, 1.14.0, 1.14.1, 1.14.2, 1.14.3, 1.14.4, 1.14.5, 1.14.6, 1.15.0, 1.15.1, 1.15.2, 1.15.3, 1.15.4, 1.16.0, 1.16.1, 1.16.2, 1.16.3, 1.16.4, 1.16.5, 1.16.6, 1.17.0, 1.17.1, 1.17.2, 1.17.3, 1.17.4, 1.17.5, 1.18.0, 1.18.1, 1.18.2, 1.18.3, 1.18.4, 1.18.5, 1.19.0, 1.19.1, 1.19.2, 1.19.3, 1.19.4, 1.19.5, 1.20.0, 1.20.1, 1.20.2, 1.20.3, 1.21.0, 1.21.1, 1.22.0, 1.22.1, 1.22.2, 1.22.3, 1.22.4, 1.23.0rc1, 1.23.0rc2, 1.23.0rc3, 1.23.0, 1.23.1, 1.23.2, 1.23.3, 1.23.4, 1.23.5, 1.24.0rc1, 1.24.0rc2, 1.24.0, 1.24.1, 1.24.2, 1.24.3, 1.24.4, 1.25.0rc1, 1.25.0, 1.25.1, 1.25.2, 1.26.0b1)

これを参考に、必要なバージョンを指定してインストールする。

(test) $ pip install "numpy==1.25.1"

パッケージを削除したければ、以下のようにする。

(test) $ pip uninstall numpy

環境の再現

仮想環境のパッケージインストール状況を、他のマシンなどで再現したい場合がある。その場合は、まずパッケージリストを以下のように取得する。

(test) $ pip freeze > requirements.txt

requirements.txt は、たとえば以下のような内容になっている。

beautifulsoup4==4.12.2
bs4==0.0.1
certifi==2023.7.22
charset-normalizer==3.2.0
idna==3.4
markdown-it-py==3.0.0
mdurl==0.1.2
numpy==1.25.2
pip-search==0.0.12
Pygments==2.16.1
requests==2.31.0
rich==13.5.2
soupsieve==2.4.1
urllib3==2.0.4

別の仮想環境でパッケージの状況を再現するには、次のようにすればよい。

$ pip install -r requirements.txt

ドキュメント

クラスなどの特定の場所に文字列を置いておくと、それがドキュメントになる。これは docstring と呼ばれる。

class Prof:
    """
    猫ちゃんのプロフィール管理クラス
    """
    def __init__(self, name, type, age):
        ...

これをインポートしてヘルプを見ると、説明が出てくる。

>>> import prof
>>> help(prof.Prof)

Help on class Prof in module prof:

class Prof(builtins.object)
 |  Prof(name, type, age)
 |  
 |  猫ちゃんのプロフィール管理クラス
 |  
 |  Methods defined here:
    ...

main 関数

Python スクリプトは、上から順に実行されるが、C 言語のように main() 関数から始めたい場合がある。そういうときは、次のような書き方がある。

def main():
    pass

if __name__ == "__main__":
    main()

__name__ は、トップレベルで実行されているとき、つまりスクリプトが直接実行されているときは、値が "__main__" になっている。したがって、この値を調べて、"__main__" だったら main() を呼ぶ、としておけば、C 言語の main() のようなことができる。

また、モジュールにこれを使うと、スクリプトを直接実行したときはテストを実行し、モジュールとして読み込まれたときは何もしない、ということができる。

コマンドライン引数

コマンドラインからスクリプトと引数を指定した場合、それらは sys.argv で取得できる。

args.py

import sys
print(sys.argv)
$ python args.py 1 2 3
['args.py', '1', '2', '3']

時間の計測

コード内である間の時間を計測する場合は、次のようにする。

import time

start_time = time.time()

...

end_time = time.time()
elapsed_time = end_time - start_time

print("Elapsed Time: %f s" % elapsed_time)

ある関数の性能を測りたい場合は、timeit モジュールを用いる方法がある。

$ python -m timeit -n 5 -n 5 -s "import fibonacci" "fibonacci.fibonacci(30)"
5 loops, best of 5: 111 msec per loop

オプション "-n" は指定した文を何回実行するか、"-r" は何回繰り返すかを指定する。"-s" は初めに 1 回だけ実行される文である。"-n 5 -r 5" だと、"-n" で指定した 5 回で平均値を取って、それを "-r" で指定した 5 回だけ繰り返し、その中の最小値を最後に表示する。

プロファイリングするには、cProfile モジュールを用いる。

fibonacci.py

def fibonacci(n):
    if n == 0: return 0
    elif n == 1: return 1
    else: return fibonacci(n - 2) + fibonacci(n - 1)

if __name__ == "__main__":
    print(fibonacci(30))
$ python -m cProfile fibonacci.py
832040
         2692541 function calls (5 primitive calls) in 0.578 seconds

   Ordered by: cumulative time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    0.578    0.578 {built-in method builtins.exec}
        1    0.000    0.000    0.578    0.578 fibonacci.py:1()
2692537/1    0.578    0.000    0.578    0.578 fibonacci.py:1(fibonacci)
        1    0.000    0.000    0.000    0.000 {built-in method builtins.print}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}

それぞれの項目は順番に以下の通りである。

  • ncalls: 関数が呼ばれた回数
  • tottime: 関数でかかった全時間
  • percall: 関数 1 回分の時間
  • cumtime: 関数が呼んだ関数の時間まで含めた累積時間
  • percall: 関数 1 回分の累積時間

文字列

文字列の変換

文字列は文字を変更できない。文字列を編集したければ変換するしかない。そのためには、一旦リストに変換したほうがよいかもしれない。

>>> list("abc")
['a', 'b', 'c']

ただ、どうせループを回すなら、直接回すことができる。

>>> for c in "abc":
...     print(c)
... 
a
b
c

たとえば、小文字を大文字に変換する関数を雑に作るとすると、次のようになるだろう。

>>> def f(x):
...     r = []
...     for c in x:
...             if "a" <= c <= "z":
...                     r.append(chr(ord(c) + (ord("A") - ord("a"))))
...             else:
...                     r.append(c)
...     return "".join(r)
... 
>>> f("abc")
'ABC'

>>> f("aB6")
'AB6'

"".join(r) というのは、リストの文字を指定の文字列で連結する (この場合だと隙間なく連結する) という意味である。

文字列を繰り返す

文字列を繰り返すには、* を用いる。

>>> "="*20
'===================='

連結と分割

リストを文字列として連結したり、文字列をリストに分割したりするには、それぞれ join()、split() を用いる。

>>> ",".join(["sabatora", "chatora", "mike"])
'sabatora,chatora,mike'

>>> "sabatora,chatora,mike".split(",")
['sabatora', 'chatora', 'mike']

文字を探す

文字列から文字列を探すには、find()、rfind() を使う。

>>> "penguin".find("n")
2

>>> "penguin".rfind("n")
6

rfind() は左から探す。見つからなければ -1 を返す。

>>> "penguin".find("x")
-1

似たようなもので index()、rindex() というものがあるが、こちらは見つからなかったときに例外を投げるところが異なる。

>>> "penguin".index("n")
2

>>> "penguin".rindex("n")
6

>>> "penguin".index("x")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: substring not found

文字列がマッチした数を数えるなら、count() がある。

>>> "penguin".count("n")
2

正規表現

正規表現を扱うには、re モジュールを用いる。

>>> import re

search() で条件に合致しているものを取り出したりできる。

>>> r = re.search(r"\s(p.*)[\s\.]", "This is a penguin.")
>>> r[1]
'penguin'

括弧でくくったところ (この場合は "(p.*)") を取り出せる。ここで、正規表現の文字列を r"..." としているのは、エスケープ文字と矛盾を起こすことを避けるためである。

似たものに match() があるが、こちらは先頭が合致するかを調べる。

sub() で文字の置き換えができる。

>>> re.sub(r"\s*$", "", "This is a penguin.    ")
'This is a penguin.'

リスト

大きさだけ確保する

とりあえずリストの大きさだけ確保したければ、次のようにすればよい。

>>> a = [0]*3
>>> a
[0, 0, 0]

連結する

+ で連結できる。

>>> a = [1, 2, 3]
>>> b = [4, 5, 6]
>>> a + b
[1, 2, 3, 4, 5, 6]

最初と最後を取り出す

リストの最初と最後の要素を取り出したければ、添字で取り出せる。

>>> a = list(range(1, 101))
>>> a[:3]
[1, 2, 3]

>>> a[-3:]
[98, 99, 100]

:3 は 0、1、2、-3: は -3、-2、-1 となる。-1 が最後の要素の位置である。

要素が含まれるかどうか

要素が含まれるかどうかは、in でわかる。

>>> "a" in ["a", 2, "?"]
True

>>> 2 in ["a", 2, "?"]
True

>>> 1 in ["a", 2, "?"]
False

含まれないかどうかは、not in でよい。

>>> 1 not in ["a", 2, "?"]
True

要素の並び替え

要素を並び替えるには sort() を用いる。

>>> a = [1, 2, 3, 5, 6, 4]
>>> a.sort()
>>> a
[1, 2, 3, 4, 5, 6]

もとのリストを並び替えたくない場合は、sorted() で並び替えた結果だけ得られる。

>>> sorted(a)
[1, 2, 3, 4, 5, 6]

>>> a
[1, 2, 3, 5, 6, 4]

逆順にもできる。

>>> a = [1, 2, 3, 5, 6, 4]
>>> a.sort(reverse=True)
>>> a
[6, 5, 4, 3, 2, 1]

あるいは

>>> sorted(a, reverse=True)
[6, 5, 4, 3, 2, 1]

>>> a
[1, 2, 3, 5, 6, 4]

単純に逆にするだけなら reverse() でよい。

>>> a.reverse()
>>> a
[4, 6, 5, 3, 2, 1]

あるいは

>>> list(reversed(a))
[4, 6, 5, 3, 2, 1]

重複要素の削除

重複要素を削除するには、一旦集合にしてしまえばよい。

>>> a = [1, 2, 2, 5, 4, 6, 3]
>>> list(set(a))
[1, 2, 3, 4, 5, 6]

最大値と最小値

要素の最大値、最小値を得るには、それぞれ max()、min() を用いる。

>>> a = [random() for i in range(5)]
>>> a
[0.8983444106998932, 0.8877671668211103, 0.22390006005592378, 0.28343861333958353, 0.20320487776814444]

>>> max(a)
0.8983444106998932

>>> min(a)
0.20320487776814444

添字がほしい場合は、index() を用いる。

>>> a.index(max(a))
0

>>> a.index(min(a))
4

絶対値

要素の絶対値がほしい場合は、次のようにすればよい。

>>> a = [1, -2, 3, -4, 5]
>>> [abs(x) for x in a]
[1, 2, 3, 4, 5]

合計

sum() で合計を取ることができる。

>>> a = [1, 2, 3, 4, 5]
>>> sum(a)
15

積

math.prod() で各要素の積を取ることができる。

>>> a = [1, 2, 3, 4, 5]
>>> from math import prod
>>> prod(a)
120

演算の積算

functools の reduce() を用いると、ある演算子を要素に適用した結果などが得られる。

>>> a = [1, 2, 3, 4, 5]
>>> from functools import reduce
>>> import operator
>>> reduce(operator.add, a)
15

>>> reduce(operator.mul, a)
120

ここで、operator.add や operator.mul は、+ や * の関数である。

初期値を指定できる。

>>> reduce(operator.mul, a, -1)
-120

積算の途中がほしい場合は、itertools の accumulate() を用いる。

>>> from itertools import accumulate
>>> import operator
>>> list(accumulate([1, 2, 3, 4, 5], operator.add))
[1, 3, 6, 10, 15]

>>> from itertools import accumulate
>>> import operator
>>> a = list(accumulate([1, 2, 3, 4, 5], operator.mul))
>>> a
[1, 2, 6, 24, 120]

階差

階差を作るには、次のようにすればよい。

>>> def f(a, b):
...    def g(v):
...            nonlocal a, b
...            b = a
...            a = v
...            return a - b
...    return g
...
>>> list(map(f(0, 0), a))[1:]
[1, 4, 18, 96]

ここまで来たら普通にループで書いたほうがよさそう。

>>> b = []
>>> e0 = a[0]
>>> for e in a[1:]:
...    b.append(e - e0)
...    e0 = e
...
>>> b
[1, 4, 18, 96]

各要素への関数の適用

リストの各要素へ関数を適用して別のリストを作るには、map() を用いる。

>>> a = list(range(1, 6))
>>> a
[1, 2, 3, 4, 5]

>>> list(map(lambda x: 2*x + 1, a))
[3, 5, 7, 9, 11]

返り値を気にしなければ、一般の関数の適用にも使えるかもしれない。

>>> b = []
>>> list(map(lambda x: b.append(2*x + 1), range(1, 6)))
[None, None, None, None, None]

>>> b
[3, 5, 7, 9, 11]

抽出

filter() で条件に合った要素だけを取り出すことができる。

>>> a = list(range(1, 11))
>>> a
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

>>> list(filter(lambda x: x % 2 == 0, a))
[2, 4, 6, 8, 10]

内包表記でもよい。

>>> [x for x in a if x % 2 == 0]
[2, 4, 6, 8, 10]

条件に合う場所の添字がほしい場合も、内包表記でいける。

>>> a = list(range(2, 11))
>>> a
[2, 3, 4, 5, 6, 7, 8, 9, 10]

>>> [i for i, x in enumerate(a) if x % 2 == 0]
[0, 2, 4, 6, 8]

条件判定

どれかの要素で条件が成り立つかを調べるには any() が使える。また、すべての要素で条件が成り立つかどうかは all() でわかる。

>>> a = [1, 3, 5, 8, 9]
>>> any(map(lambda x: x % 2 == 1, a))
True

>>> all(map(lambda x: x % 2 == 1, a))
False

>>> a = [1, 3, 5, 7, 9]
>>> all(map(lambda x: x % 2 == 1, a))
True

条件に合うものが何個あるかを数えるには count() を使う。

>>> a = [1, 3, 5, 8, 9, 10]
>>> a.count(1)
1
>>> list(map(lambda x: x % 2 == 0, a)).count(True)
2

要素の置き換え

条件に合った要素を別の値で置き換えるのは、map() でできる。

>>> a = list(map(lambda x: None if x % 2 == 0 else x, [1, 2, 3, 4, 5]))
>>> a
[1, None, 3, None, 5]

>>> list(map(lambda x: 0 if x is None else x, a))
[1, 0, 3, 0, 5]

ファイル

ファイルを開くには open() を使う。

>>> f = open("test.txt", "w")
>>> f.write("test")
4

>>> f.close()

>>> f = open("test.txt", "r")
>>> f.read()
'test'

>>> f.close()

次のような書き方もできる。

>>> with open("test.txt", "r") as f:
...     f.read()
... 
'test'

上の場合、ファイルは自動的に閉じられる。

全体を読み込むには readlines() が使える。

>>> with open("test.txt", "w") as f:
...     f.write("aaa\n")
...     f.write("bbb\n")
...     f.write("ccc\n")
... 
4
4
4

>>> with open("test.txt", "r") as f:
...     lines = f.readlines()
... 
>>> lines
['aaa\n', 'bbb\n', 'ccc\n']

1行ずつ読み込んでファイルの最後まで処理するなら、次のような方法もある。

>>> with open("test.txt", "r") as f:
...     for line in f:
...             print(line, end="")
... 
aaa
bbb
ccc

書式

文字列に対してタプルを % でつなぐ形で C 言語の printf() のような書式を利用できる。

>>> a = 1.23456
>>> print("a = %.3f" % a)
a = 1.235

>>> b = "m"
>>> print("a = %.3f [%s]" % (a, b))
a = 1.235 [m]

format() を使う方法もある。

>>> print("a = {:.3f}".format(a))
a = 1.235

f"..." を用いる方法もある。これは、変数を直接埋め込める。

>>> print(f"a = {a:.3f}")
a = 1.235

参考

  • Guido van Rossum: Python チュートリアル 第 4 版, オライリー・ジャパン (2021).
  • Bill Lubanovic: 入門 Python 3 第 2 版, オライリー・ジャパン (2021).