●モジュール
- Python のモジュール (module) はとても簡単で、ソースファイルが一つのモジュールになる
- 拡張子 .py を除いたファイル名がモジュール名になる
- モジュールを利用する場合は import 文を使う
import module_name, ...
複数のモジュールをインポートするときはカンマ ( , ) で区切って指定する
モジュールはインポートされたときにモジュール内のプログラムを実行する
モジュールはモジュール sys の変数 path のリストに定義されているディレクトリに配置すること
モジュール内に定義された変数や関数にアクセスするには、名前の前にモジュール名とドット ( . ) を付ける
リスト : モジュール foo (foo.py)
a = 10
def test():
print('module foo')
リスト : モジュール bar (bar.py)
a = 100
def test():
print('module bar')
>>> import foo, bar
>>> foo.a
10
>>> foo.test()
module foo
>>> bar.a
100
>>> bar.test()
module bar
from 文を使うと他のモジュールで定義されている名前をそのまま自分のモジュールで利用することができる
from module_name import name, ...
名前の衝突がない場合、from 文を使うとモジュール名を付けなくてすむ
>>> from foo import a, test
>>> a
10
>>> test()
module foo
名前の指定にアスタリスク (*) を指定すると、モジュール内で定義されているすべての名前を利用することができる
この方法はあまり推薦されていないようだ
>>> from bar import *
>>> a
100
>>> test()
module bar
モジュール名は変数 __name__ に格納される
モジュールではなくスクリプトとして実行すると、__name__ の値は '__main__' になる
__name__ の値をチェックすることで、スクリプトのときだけ実行する処理 (たとえばテストなど) を記述することができる
リスト : モジュール baz (baz.py)
a = 10
def test():
print('module baz')
print(__name__)
if __name__ == '__main__':
print("Go, Go, Python3!!!")
>>> import baz
baz
C>python baz.py
__main__
Go, Go, Python3!!!
●パッケージ
- パッケージ (package) は複数のモジュールをまとめたもの
- Python のパッケージはディレクトリ (フォルダ) で管理する
- ディレクトリの中にファイル __init__.py を設置すれば、そのディレクトリがパッケージになる
- このとき、ディレクトリ名がパッケージ名になる
- ディレクトリと同様にパッケージも階層的な構造になる
- パッケージ内のモジュールは パッケージ1.パッケージ2.モジュール のようにドットを付けてアクセスする
- . (カレントパッケージ) と .. (親パッケージ) を使ってモジュールのパスを相対的に指定することもできる
- __init__.py はパッケージの初期化処理を記述する (空のファイルでもかまわない)
- たとえば、サブディレクトリ bar の中に __init__.py, foo.py, bar.py をセットする
C>dir bar
C>dir /B bar
bar.py
foo.py
__init__.py
>>> import bar.foo, bar.bar
>>> bar.foo.a
10
>>> bar.foo.test()
module foo
>>> bar.bar.a
100
>>> bar.bar.test()
module bar
もちろん from 文も使うことができる
from パッケージ import モジュール, ...
from パッケージ.モジュール import 名前, ...
>>> from bar import foo, bar
>>> foo.a
10
>>> foo.test()
module foo
>>> bar.a
100
>>> bar.test()
module bar
>>> from bar.foo import a, test
>>> a
10
>>> test()
module foo
from パッケージ import * でモジュールをインポートするときは __init__.py の変数 __all__ にモジュール名のリストをセットする
リスト : __init__.py
__all__ = ["foo", "bar"]
>>> from bar import *
>>> foo.a
10
>>> foo.test()
module foo
>>> bar.a
100
>>> bar.test()
module bar
●ファイル入出力
- 標準入出力
- Python は「ファイルオブジェクト」というデータを介してファイルにアクセスする
- 標準入出力は Python の起動時にファイルオブジェクトが自動的に生成される
- 標準入出力に対応するファイルオブジェクトは、モジュール sys の変数に格納されている
- sys.stdin, 標準入力
- sys.stdout, 標準出力
- sys.stderr, 標準エラー出力
- 関数 input() は標準入力から 1 行読み込む (改行文字は削除される)
- Python2 の raw_input() と同じ
- 引数に文字列を渡すと、それをプロンプトとして表示する
>>> input()
foo
'foo'
>>> input()
1234
'1234'
>>> input('>>> ')
>>> hello, world
'hello, world'
関数 print() はデータを引数 file のファイルオブジェクトに出力する
Python2 の print は文で、Python3 の print は関数
print(*args, file=sys.stdout, sep=' ', end='\n', flush=False)
複数のデータを渡すと、データとデータの間に引数 sep を挿入する
print() はデータを出力したあと引数 end を出力する
改行したくない場合は end に異なるデータ (たとえば空文字列など) を指定する
>>> print('hello, world')
hello, world
>>> print(1, 2, 3, 4, 5)
1 2 3 4 5
>>> print(1, 2, 3, 4, 5, sep=',')
1,2,3,4,5
>>> print(1, 2, 3, 4, 5, sep=',', end=' ')
1,2,3,4,5 >>>
ファイルのアクセス
- ファイルのオープン, open(filename, mode) => file_object
- mode はアクセスモードでC言語とほとんど同じ
- r, 読み込み (read) モード
- w, 書き出し (write) モード
- a, 追加 (append) モード
- + を付加すると更新モード
- b を付加するとバイナリモード
- mode を省略すると 'r' になる
- ファイルのクローズ, close(file_object)
- with 文を使うと、自動的にファイルをクローズすることができる
with open(filename, mode) as 変数:
処理
...
open() で生成されたファイルオブジェクトは as の後ろの変数にセットされる
with 文の処理を終了すると (エラーで抜ける場合も)ファイルはクローズされる
with 文は「例外処理」で説明する
入出力用の主なメソッド
- read(size), size バイト読み込んで文字列にして返す
- readline(), 1 行読み込んで文字列にして返す (改行を含む)
- readlines(), ファイルのすべての行を読み込んでリストに格納して返す
- write(s), 文字列 s をファイルに書き込む
- writelines(x), リスト x に格納された文字列をファイルに書き込む
ファイルオブジェクトはイテレータ
for 文 (または next()) で 1 行ずつ読み込むこともできる
>>> with open('test.txt', 'w') as f:
... for x in range(10):
... f.write(str(x) + '\n')
...
2
2
2
2
2
2
2
2
2
2
>>> with open('test.txt') as f:
... for x in f: print(x, end='')
...
0
1
2
3
4
5
6
7
8
9
>>> a = ['foo\n', 'bar\n', 'baz\n', 'oops\n']
>>> with open('test1.txt', 'w') as f: f.writelines(a)
...
>>> with open('test1.txt') as f: f.readlines()
...
['foo\n', 'bar\n', 'baz\n', 'oops\n']
Python の場合、モジュール sys の変数 argv にコマンドラインで与えられた引数が格納される
リスト : コマンドライン引数 (test01.py)
import sys
print(sys.argv)
C>python test01.py foo bar baz
['test01.py', 'foo', 'bar', 'baz']
リストの先頭要素は実行したスクリプト test01.py になる
●文字列のフォーマット
- データを整形して出力する場合、Python2 ではC言語によく似た書式指定を用いるが、Python3 では推薦されていない
- Python3 にはいくつか方法があるが、ここでは str.format() を紹介する
書式文字列.format(引数, ...)
書式文字列の {} は format() の引数を文字列に変換する
{n} とすると n 番目の引数を変換する
{:変換指示子}
- {:b}, 2 進数表示
- {:o}, 8 進数表示
- {:d}, 10 進数表示
- {:x}, {:X}, 16 進数表示 (英小文字, 英大文字)
- {:#}, 引数が整数で指示子が b, o, x, X の場合、接頭辞 (0b, 0o, 0x, 0X) を付ける
- {:e}, {:E}, 指数表示 (英小文字, 英大文字)
- {:f}, {:F}, 小数点表示
- {:g}, {:G}, 汎用フォーマット (桁数に応じて {:e} または {:f} で表示)
- {:c}, 文字 (数値を対応する文字 (Unicode) に変換する)
- {:s}, 文字列 (デフォルト値)
: と変換指示子の間に桁数などを指定することができる
- {:n}, n 桁表示
- 引数が数値の場合、以下のオプションがある
- {:0n}, 空欄を 0 で埋める
- {:.mf}, {:n.mf}, 小数点以下を m 桁表示
- {:+}, 先頭に符号 (+, -) を付ける
- {:-}, 負数の場合符号 - を付ける
- {: }, 正数の場合は空白を、負数の場合は符号 - を付ける
- {:<n}, 左詰め
- {:>n}, 右詰め
- {:^n}, 中央
- <, > ^ の前に文字を指定すると、空欄をその文字で埋める
この他にもいろいろな機能がある
詳細はライブラリリファレンス「書式指定文字列の文法」を参照
>>> '{} {} {}'.format(1, 2, 3)
'1 2 3'
>>> '{1} {2} {0}'.format(1, 2, 3)
'2 3 1'
>>> for x in range(16): print('{} {:b} {:o} {:x}'.format(x,x,x,x))
...
0 0 0 0
1 1 1 1
2 10 2 2
3 11 3 3
4 100 4 4
5 101 5 5
6 110 6 6
7 111 7 7
8 1000 10 8
9 1001 11 9
10 1010 12 a
11 1011 13 b
12 1100 14 c
13 1101 15 d
14 1110 16 e
15 1111 17 f
>>> '{:e} {:f} {:g}'.format(1.2345, 1.2345, 1.2345)
'1.234500e+00 1.234500 1.2345'
>>> '{:e} {:f} {:g}'.format(1.2345e10, 1.2345e10, 1.2345e10)
'1.234500e+10 12345000000.000000 1.2345e+10'
>>> '{:9}'.format('abc')
'abc '
>>> '{:<9}'.format('abc')
'abc '
>>> '{:>9}'.format('abc')
' abc'
>>> '{:^9}'.format('abc')
' abc '
●簡単なプログラム (2)
リスト : cat.py
import sys
def cat(filename):
with open(filename) as f:
for xs in f: print(xs, end='')
if len(sys.argv) >= 2:
for filename in sys.argv[1:]:
cat(filename)
else:
print('usage: python3 cat.py file ...')
行の連結
リスト paste.py
import sys
def output(f):
for xs in f: print(xs, end='')
def paste(f1, f2):
while True:
xs = f1.readline().rstrip('\n')
ys = f2.readline().rstrip('\n')
if xs and ys:
print(xs, ys)
elif xs:
print(xs)
output(f1)
break
else:
print(ys)
output(f2)
break
if len(sys.argv) >= 3:
with open(sys.argv[1]) as f1:
with open(sys.argv[2]) as f2:
paste(f1, f2)
else:
print('usage: python3 paste.py file1 file2')
- 改行は文字列のメソッド rstrip('\n') で削除できる
- 拙作のページ「Python 入門 第 3 回: 文字の除去」を参照
単語のカウント
リスト : wc.py
import sys
def wc (fin, filename=''):
size = 0
word = 0
line = 0
for xs in fin:
size += len(xs.encode())
line += 1
word += len(xs.split())
print(line, word, size, filename)
if len(sys.argv) <= 1:
wc(sys.stdin)
else:
with open(sys.argv[1]) as f:
wc(f, sys.argv[1])
- Python3 の場合、文字列 str に len() を適用すると文字数が返る (python2 はバイト数)
- バイト数を求めたい場合はバイトオブジェクト (bytes) に変換する
- bytes はバイトシーケンスを表すデータ型
- bytes は b'...' のように文字列リテラルの前に b を付ける
- メソッド str.encode(encoding='utf-8') は文字列 str を bytes に変換する
- メソッド bytes.decode(encoding='utf-8') は bytes を str に変換する
>>> a = 'あいうえお'
>>> b = a.encode()
>>> b
b'\xe3\x81\x82\xe3\x81\x84\xe3\x81\x86\xe3\x81\x88\xe3\x81\x8a'
>>> b.decode()
'あいうえお'
str.split() は文字列 str を区切り記号で分解し、リストに格納して返す
拙作のページ「Python 入門 第 3 回: 文字列の分解と結合」を参照
タブを空白に展開
リスト : expand.py
import sys
def expand(f):
col = 0
while True:
c = f.read(1)
if c == '': break
if c == '\t':
while True:
sys.stdout.write(' ')
col += 1
if col % 8 == 0: break
else:
if c == '\n':
col = 0
else:
col += 1
sys.stdout.write(c)
def expand1(f):
pat = re.compile(b'\t+')
for xs in f:
xs = xs.encode()
while True:
m = pat.search(xs)
if not m: break
n = (m.end() - m.start()) * 8 - m.start() % 8
xs = pat.sub(b' ' * n, xs, 1)
print(xs.decode(), end='')
if len(sys.argv) > 1:
with open(sys.argv[1]) as f:
expand1(f)
else:
expand1(sys.stdin)
- ASCII コードのみ対応
- 関数 expand() は read() と write() を使って 1 byte ずつデータを読み書きしている
- あまり Python らしくないプログラム
- 関数 expand1() は 1 行ずつ読み込み、正規表現を使ってタブを展開している
空白をタブに置換
リスト : unexpand.py
import sys
def unexpand(f):
col = 0
while True:
sc = 0
c = f.read(1)
if c == '': break
if c == ' ':
while True:
sc += 1
col += 1
if col % 8 == 0:
sys.stdout.write('\t')
sc = 0
c = f.read(1)
if c != ' ': break
for _ in range(sc): sys.stdout.write(' ')
if c == '\n':
col = 0
else:
col += 1
sys.stdout.write(c)
def unexpand1(f):
pat = re.compile(b' +$')
for xs in f:
xs = xs.rstrip('\n')
xs = xs.encode()
i = 0
while i < len(xs):
ys = xs[i : i + 8]
if len(ys) == 8:
print(pat.sub(b'\t', ys).decode(), end='')
else:
print(ys.decode())
i += 8
if len(sys.argv) > 1:
with open(sys.argv[1]) as f:
unexpand1(f)
else:
unexpand1(sys.stdin)
- ASCII コードのみ対応
- 関数 unexpand() は read() と write() を使って 1 byte ずつデータを読み書きしている
- あまり Python らしくないプログラム
- 関数 unexpand1() は 1 行ずつ読み込み、正規表現を使って空白をタブに置換している
文字列の検索
リスト : grep.py
import sys, re
if len(sys.argv) >= 3:
p = re.compile(sys.argv[1])
with open(sys.argv[2]) as f:
n = 1
for x in f:
if p.search(x): print('{:6}: {}'.format(n, x), end='')
n += 1
else:
print('usage: python3 grep.py str file')
- 正規表現は拙作のページ「Python 入門 第 3 回」を参照
文字列の置換
リスト : gres.py
import sys, re
if len(sys.argv) >= 4:
pat1 = re.compile(sys.argv[1])
pat2 = sys.argv[2]
with open(sys.argv[3]) as f:
for x in f:
print(pat1.sub(pat2, x), end='')
else:
print('usage: python3 gres.pl search replace file')
- 正規表現による文字列の置換は、拙作のページ「Python 入門 第 3 回: 文字列の置換」を参照
ファイルのエントロピー
リスト : entoropy.py
def entoropy(name):
count = [0] * 256
with open(name, "rb") as f:
while True:
c = f.read(1)
if not c: break
count[ord(c)] += 1
total = float(sum(count))
e = 0.0
for x in range(256):
if not count[x]: continue
p = count[x] / total
e += - p * math.log(p, 2)
return e, e * total / 8
if len(sys.argv) > 1:
print(entoropy(sys.argv[1]))
else:
print('usage: python3 enotropy.py file')
- ファイルのエントロピーを計算するプログラム
- エントロピーは拙作のページ「Algorithms with Python: シャノン・ファノ符号とハフマン符号」を参照
ランレングス符号化
リスト : rle.py
import sys
def rle(fin, fout):
c = fin.read(1)
while c:
num = 1
while num < 256:
c1 = fin.read(1)
if c != c1: break
num += 1
fout.write(c)
fout.write((num - 1).to_bytes(1, 'big'))
if num == 256:
c = fin.read(1)
else:
c = c1
if len(sys.argv) > 2:
with open(sys.argv[1], 'rb') as fin:
with open(sys.argv[2], 'wb') as fout:
rle(fin, fout)
else:
print('usage: python3 rle.py input_file output_file')
- ランレングス符号については拙作のページ「Algorithms with Python: 連長圧縮」を参照
- ファイルがバイナリモードでオープンされている場合、write() の引数は文字列ではなく bytes-like object であること
- 整数値を bytes に変換するにはメソッド to_bytes() を使う
- 逆に、bytes を整数値に変換するには整数 (int) のクラスメソッド from_bytes() を使う
num.to_bytes(length, byteorder)
int.from_bytes(bytes, byteorder)
byteorder は 'big' (ビッグエンディアン) または 'little' (リトルエンディアン)
>>> a = 97
>>> a.to_bytes(1, 'big')
b'a'
>>> int.from_bytes(b'a', 'big')
97
ランレングス復号
リスト : rld.py
import sys
def rld(fin, fout):
while True:
c = fin.read(1)
if not c: break
n = ord(fin.read(1)) + 1
for _ in range(n): fout.write(c)
if len(sys.argv) > 2:
with open(sys.argv[1], 'rb') as fin:
with open(sys.argv[2], 'wb') as fout:
rld(fin, fout)
else:
print('usage: python3 rld.py input_file output_file')
●クラス
class ClassName(SuperClass, ...):
処理
...
他のクラスを「継承」するときはカッコの中にクラス名を記述する
継承しない場合はカッコを省略することができる
省略すると、クラス object が暗黙のうちに継承される
>>> class Foo:
... pass
...
>>> a = Foo()
>>> a
<__main__.Foo object at 0x0000022A4216DBA8>
>>> a.x
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Foo' object has no attribute 'x'
>>> a.x = 123
>>> a.x
123
pass は何もしないことを表す文
Python はクラスを関数と同じ形式で呼び出すと、そのクラスのインスタンス (オブジェクト) を生成して返す
Python には class 文でインスタンス変数を定義する特別な構文はない
変数への代入が行われると、Python はその変数をインスタンス内に生成する
変数のアクセスは obj.name のようにドットを使う
メソッドの定義
- Python の場合、class 文の中で定義された関数がメソッドになる
- メソッドの第 1 引数にはインスタンスが渡される
- Python では、この引数名を self と記述する習慣がある
>>> import math
>>> class Point:
... def __init__(self, x, y):
... self.x = x
... self.y = y
... def distance(self, p):
... dx = self.x - p.x
... dy = self.y - p.y
... return math.sqrt(dx * dx + dy * dy)
...
>>> p1 = Point(0, 0)
>>> p2 = Point(10, 10)
>>> p1.x
0
>>> p2.y
10
>>> p1.distance(p2)
14.142135623730951
__ で始まり __ で終わる名前のメソッドには特別な機能が割り当てられる
これを「特殊メソッド」という
__init__() はインスタンスを生成するときに自動的に呼び出される特殊メソッド
Python では __init__() で必要なインスタンス変数を初期化するのが一般的
メソッドのアクセスはインスタンス変数と同じ形式で、メソッド名の後ろにカッコを付けて呼び出す
ポリモーフィズム
- obj.method() は obj が属するクラスに定義されているメソッドを呼び出す
>>> class Point3d:
... def __init__(self, x, y, z):
... self.x = x
... self.y = y
... self.z = z
... def distance(self, p):
... dx = self.x - p.x
... dy = self.y - p.y
... dz = self.z - p.z
... return math.sqrt(dx * dx + dy * dy + dz * dz)
...
>>> p3 = Point3d(0, 0, 0)
>>> p4 = Point3d(10, 10, 10)
>>> p3.distance(p4)
17.320508075688775
>>> p1.distance(p2)
14.142135623730951
Point と Point3d には同じ名前のメソッド distance() がある
p3 は Point3d のインスタンスなので、p3.distance(p4) は Point3d の distance() が呼び出される
p1 は Point のインスタンスなので、p1.distance(p2) は Point の distance() が呼び出される
このように、ドットの左側のインスタンスによって適切なメソッドが呼び出される
このような機能を「ポリモーフィズム」という
ポリモーフィズムはオブジェクト指向にとってとても重要な機能
クラス変数
- クラスで共通の変数や定数を使いたい場合は、class 文の中で変数を定義する
- これを「クラス変数」という
- クラス変数は クラス名 + ドット ( . ) + 変数名 でアクセスする
>>> class Foo:
... z = 1
...
>>> Foo.z
1
>>> Foo.z = 10
>>> Foo.z
10
>>> a = Foo()
>>> b = Foo()
>>> a.z
10
>>> b.z
10
クラス変数はインスタンスからも参照することができる
ただし、同名のインスタンス変数があると、インスタンスからクラス変数を参照することはできない
>>> a.z = 100
>>> b.z
10
>>> Foo.z
10
>>> a.z
100
a.z = 100 はインスタンス変数 z に 100 を代入する
b.z, Foo.z はクラス変数 z を参照するので 10 のまま
a.z はインスタンス変数 z を参照するので 100 になる
クラスメソッド
- 今まで定義したメソッドはインスタンスを操作する
- これを「インスタンスメソッド」という
- これに対し、クラスの動作にかかわるメソッドを定義することもできる
- これを「クラスメソッド」という
- クラスメソッドの定義はデコレータ @classmethod を使う
>>> class Foo:
... z = 1
... @classmethod
... def get_z(cls): return cls.z
... @classmethod
... def set_z(cls, x): cls.z = x
...
>>> Foo.get_z()
1
>>> Foo.set_z(10)
>>> Foo.get_z()
10
メソッド get_z(), set_z() の前に @classmethod を書くだけ
第 1 引数の cls にドットの左側のクラスが渡される
Python では、インスタンス変数やクラス変数、メソッドのことを「属性 (attribute)」という
属性は名前で管理されていて、同じ名前のインスタンス変数とメソッドを同時に使うことはできない
インスタンス変数の方が優先されるため、メソッドを呼び出すことができなくなる
インスタンス変数はクラス変数やメソッドよりも優先されることに注意
●継承
- 「継承 (inheritance : インヘリタンス)」は簡単に言うとクラスに「親子関係」を持たせる機能
- 子供のクラス (サブクラス) は親クラス (スーパークラス) の性質を受け継ぐことができる
- プログラミング言語の場合、引き継ぐ性質は定義されたインスタンス変数やメソッドになる
- ただし、Python ではインスタンス変数の継承は行われない
- 継承されるのはメソッドとクラス変数だけなので注意すること
- スーパークラスは class 文のカッコで指定する
- ただ一つのクラスを継承することを「単一継承」、複数のクラスを継承することを「多重継承」という
- Python は多重継承をサポートしているので、カッコ内に複数のスーパークラスを指定することができる
- 多重継承はあとで説明する
>>> class Foo:
... def __init__(self, a, b):
... self.a = a
... self.b = b
... def get_a(self): return self.a
... def get_b(self): return self.b
...
>>> class Bar(Foo):
... def __init__(self, a, b, c):
... super().__init__(a, b)
... self.c = c
... def get_c(self): return self.c
...
>>> a = Foo(1, 2)
>>> b = Bar(10, 20, 30)
>>> a.get_a()
1
>>> b.get_a()
10
>>> a.get_b()
2
>>> b.get_b()
20
>>> b.get_c()
30
クラス Foo にはインスタンス変数 a, b とメソッド get_a(), get_b() が定義されている
Bar は Foo を継承し、Bar 固有のインスタンス変数 c とメソッド get_c() を定義する
クラス Foo を継承することにより、メソッド get_a() と get_b() を利用することができる
サブクラスでスーパークラスのメソッドと同名のメソッドを定義することができる
これを「オーバーライド (over ride))」という
オーバーライドしたメソッドからスーパークラスのメソッドを呼び出すときは super() を使う
super().method(args, ...)
super() はスーパークラスの method() を呼び出す
このとき、method() の第 1 引数 (self) には、オーバーライドしたメソッドの self が渡される
super().__init__(a, b) とすることで、Foo のメソッド __init__() を呼び出すことができる
__init__() をオーバーライドしない場合、スーパークラスの __init__() が自動的に呼び出される
>>> class Bar1(Foo):
... def get_c(self): return self.a + self.b
...
>>> c = Bar1(100, 200)
>>> c.get_c()
300
クラス Bar1 は __init__() をオーバーライドしていない
Bar1 のインスタンスを生成すると、Foo の __init__() が呼び出されて a と b が初期化される
メソッド get_c() を呼び出すと a と b を足した値を返す
クラス変数は継承される
>>> class Foo:
... z = 1
...
>>> class Bar(Foo):
... pass
...
>>> Foo.z
1
>>> Bar.z
1
Bar.z で Foo のクラス変数 z を参照できる
Bar.z に代入すると、Bar にクラス変数 z が生成されるため、Foo のクラス変数 z は参照できなくなる
インスタンスの型は関数 isinstance() で、クラスの継承関係は関数 issubclass() で調べることができる
isinstance(object, cls) => bool
issubclass(cls1, cls2) => bool
isinstance() は object のクラスが cls または cls のサブクラスであれば真を返す
issubclass() は cls1 が cls2 のサブクラスであれば真を返す
>>> class Foo: pass
...
>>> class Bar(Foo): pass
...
>>> class Baz(Bar): pass
...
>>> a = Foo()
>>> b = Bar()
>>> c = Baz()
>>> isinstance(c, Foo)
True
>>> isinstance(c, Bar)
True
>>> isinstance(c, Baz)
True
>>> isinstance(b, Baz)
False
>>> issubclass(Baz, Foo)
True
>>> issubclass(Bar, Baz)
False
●連結リスト
クラスと継承の簡単な例題として、「連結リスト」とそれを継承した「制限付き連結リスト」を作成します。詳しい説明は拙作のページ「Python 入門第 5 回: 連結リスト」と「Python 入門第 6 回: 制限付きリスト」をお読みください。
表 : クラス LinkedList のメソッド
名前 | 機能 |
LinkedList(*args) | LinkedList の生成 |
ls.insert(n, x) | 連結リスト ls の n 番目に x を挿入する |
ls.at(n) | 連結リスト ls の n 番目の要素を参照する |
ls.delete(n) | 連結リスト ls の n 番目の要素を削除 |
ls.is_empty() | 連結リスト ls が空であれば真を返す |
len(ls) | 連結リストの長さ (要素数) |
ls[n], ls[n] = x, del ls[n] | 連結リスト ls の n 番目の要素の参照, 更新, 削除 |
ls1 + ls2 | 連結リストの連結 |
ls.each() | ジェネレータ |
iter(), next() | イテレータ |
str(), repr() | 文字列に変換 |
- Python3 の場合、イテレータは特殊メソッド __iter__() と __next__() を実装すること
- Python2 は __iter__() と next() だった
- 特殊メソッド __repr__() を実装すると、関数 repr() を使用できる
- 対話モードで連結リストを表示できるようになる
表 : クラス FixedList のメソッド
名前 | 機能 |
FixedList(size, *args) | 大きさ size の FixedList を生成する |
ls.insert(n, x) | n 番目に x を挿入, 挿入できない場合は None を返す |
ls.delete(n, x) | n 番目の要素を削除する |
str(), repr() | 文字列に変換 |
#
# linkedlist.py : 連結リスト
#
# Copyright (C) 2018 Makoto Hiroi
#
# 連結リストクラス
class LinkedList:
# セル
class Cell:
def __init__(self, data, link = None):
self.data = data
self.link = link
# 連結リストの初期化
def __init__(self, *args):
self.top = LinkedList.Cell(None) # ヘッダセル
for x in reversed(args):
self.insert(0, x)
# n 番目のセルを求める
def _nth(self, n):
i = -1
cp = self.top
while cp is not None:
if i == n: return cp
i += 1
cp = cp.link
return None
# 挿入
def insert(self, n, x):
cp = self._nth(n - 1)
if cp is not None:
cp.link = LinkedList.Cell(x, cp.link)
return x
return None
# 参照
def at(self, n):
cp = self._nth(n)
if cp is not None: return cp.data
return None
# 削除
def delete(self, n):
cp = self._nth(n - 1)
if cp is not None and cp.link is not None:
data = cp.link.data
cp.link = cp.link.link
return data
return None
# リストは空か
def is_empty(self): return self.top.link is None
# イテレータ
def __iter__(self):
self.index = self.top.link
return self
def __next__(self):
if self.index is None:
raise StopIteration
data = self.index.data
self.index = self.index.link
return data
# ジェネレータ
def each(self):
cp = self.top.link
while cp is not None:
yield cp.data
cp = cp.link
# リストの長さ
def __len__(self):
n = 0
for _ in self.each(): n += 1
return n
# [] による参照
def __getitem__(self, n):
cp = self._nth(n)
if cp is not None: return cp.data
raise IndexError
# [] による更新
def __setitem__(self, n, x):
cp = self._nth(n)
if cp is not None:
cp.data = x
return None
raise IndexError
# del []
def __delitem__(self, n):
if self.delete(n) is None: raise IndexError
# +
def __add__(self, y):
# リストのコピー
def copy(a):
if not a: return None
return LinkedList.Cell(a.data, copy(a.link))
# リストの連結
def append(a, b):
if a is None: return copy(b)
return LinkedList.Cell(a.data, append(a.link, b))
if not isinstance(y, LinkedList):
raise NotImplementedError
z = LinkedList()
z.top.link = append(self.top.link, y.top.link)
return z
# 表示
def __repr__(self):
if self.top.link is None: return 'LinkedList()'
s = 'LinkedList('
for x in self.each(): s += '{}, '.format(x)
return s[:-2] + ')'
def __str__(self):
return self.__repr__()
#
# 制限付き連結リスト
#
class FixedList(LinkedList):
def __init__(self, limit, *args):
self.limit = limit
self.size = 0
super().__init__(*args[:limit])
# データの挿入
def insert(self, n, x):
if self.size < self.limit:
result = super().insert(n, x)
if result is not None: self.size += 1
return result
return None
# データの削除
def delete(self, n):
if self.size > 0:
result = super().delete(n)
if result is not None: self.size -= 1
return result
return None
# 表示
def __repr__(self):
if self.top.link is None: return 'FixedList({})'.format(self.limit)
s = 'FixedList({}, '.format(self.limit)
for x in self.each(): s += '{}, '.format(x)
return s[:-2] + ')'
def __str__(self):
return self.__repr__()
# 簡単なテスト
if __name__ == '__main__':
a = LinkedList()
print(a)
print(len(a))
print(a.is_empty())
for x in range(5): a.insert(x, x)
print(a)
print(len(a))
print(a.is_empty())
for x in range(5):
print(a.at(x), a[x])
for x in range(5):
a[x] = a[x] * 10
for x in a:
print(x)
for x in a.each():
print(x)
while not a.is_empty():
# a.delete(0)
del a[0]
print(a)
a = LinkedList(1,2,3,4,5)
b = LinkedList(6,7,8,9,10)
c = a + b
print(a)
print(b)
print(c)
c[0] = 10
c[5] = 60
print(a)
print(b)
print(c)
# 制限付き連結リスト
d = FixedList(5)
print(d)
for x in range(6):
print(d.insert(0, x))
print(d)
while not d.is_empty():
print(d.delete(0))
print(d)
C>python linkedlist.py
LinkedList()
0
True
LinkedList(0, 1, 2, 3, 4)
5
False
0 0
1 1
2 2
3 3
4 4
0
10
20
30
40
0
10
20
30
40
LinkedList()
LinkedList(1, 2, 3, 4, 5)
LinkedList(6, 7, 8, 9, 10)
LinkedList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
LinkedList(1, 2, 3, 4, 5)
LinkedList(6, 7, 8, 9, 10)
LinkedList(10, 2, 3, 4, 5, 60, 7, 8, 9, 10)
FixedList(5)
0
1
2
3
4
None
FixedList(5, 4, 3, 2, 1, 0)
4
3
2
1
0
FixedList(5)