LoginSignup
2
5

More than 3 years have passed since last update.

Pythonチュートリアル ノート

Last updated at Posted at 2021-02-25

概要

Pythonチュートリアルを読みながら手元で実行したコマンドのメモ。
https://docs.python.org/ja/3/tutorial/index.html

1. やる気を高めよう

Pythonの特徴
  • インタプリタ言語なので、コンパイル・リンクが不要。電卓として使える。
  • シェルはファイル操作・テキストデータの操作に向いているけど、GUIアプリ、ゲームには向いていない。単純にシェルより機能が多い。
  • GUIのインターフェイスも標準で提供される。(Tk)
  • テキスト処理ならAwkとかPerlもあるが、それらより汎用的なデータ型が存在する。それでいて同じくらい簡単。
  • C, Javaよりコードが短くなる。より高レベルなデータ型があるのと、実行文のグループ化がインデントなので、括弧だけの行がない。
  • Cアプリケーションの拡張言語として利用可能。
  • 変数、引数宣言が不要
  • モンティパイソンの空飛ぶサーカス「Monty Python's Flying Circus」が元ネタ。

2. Python インタプリタを使う

コマンドを指定したインタプリタの起動
python -c command [arg] ...

python3 -c "print(1 + 2)"
スクリプト実行後にそのまま対話モードに入れる。-iオプション
# 2-1.py
a = 12


$ python3 -i 2-1.py
>>> a
12
>>>
コマンドライン引数

sysモジュールのargv変数に格納される。

# 2-2.py
import sys
print(sys.argv)


$ python3 2-2.py
['2-2.py']
$ python3 2-2.py asdf
['2-2.py', 'asdf']

sys[0]はスクリプトならファイル名、コマンド指定でインタプリタを起動した場合は"-c"

python3 -c "import sys; print(sys.argv)"
['-c']
文字コードを指定するにはファイルの先頭にエンコーディングを記述する。
# -*- coding:cp1242 -*-

3. 形式ばらない Python の紹介

除算と整数除算

python3から除算と整数除算の演算子が分かれて定義された。

>>> 17 / 3
5.666666666666667
>>> 17 // 3
5
冪乗
>>> 2**10
1024
対話モードで便利な「_」

対話モードでのみ、前回の計算結果が「_」に毎回代入される。_

>>> 60 * 60
3600
>>> 24 * _
86400
文字列リテラル
# 3-1.py
a = """

    hello

    world


"""

$ python3 -i 3-1.py
>>> a
'\n\n    hello\n\n    world\n\n\n'
>>>
スライス

スライスの場合のみ範囲外のインデックスでもエラーにならない。

>>> a = "abcdefg"
>>> a[3:]
'defg'
>>> a[-2]
'f'
>>> a[2]
'c'
>>> a[2:-2]
'cde'


>>> a[100]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: string index out of range
>>> a[2:100]
'cdefg'
リスト

リストの代入は参照が返るので、破壊的な操作が起き得る。

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

[https://docs.python.org/ja/3/library/copy.html#shallow-vs-deep-copy]

浅いコピーで回避可能。
スライスか、copyモジュールのcopy関数で浅いコピーを作れる。

>>> import copy as cp
>>> a = [1,2,3]
>>> b = cp.copy(a)
>>> b
[1, 2, 3]
>>> b[0] = 100
>>> a
[1, 2, 3]
>>> b
[100, 2, 3]

浅いコピーは1次元のリストのみ有効。

>>> a = [[1,2], [2,3], [3,4]]
>>> b = a[:]
>>> b
[[1, 2], [2, 3], [3, 4]]
>>> b[0][0] = 100
>>> b
[[100, 2], [2, 3], [3, 4]]
>>> a
[[100, 2], [2, 3], [3, 4]]
>>>

2次元以上のリストをコピーする場合、深いコピーが必要。
copyモジュールのdeepcopy関数で深いコピーが作れる。

>>> import copy as cp
>>> a = [[1,2],[2,3],[3,4]]
>>> b = cp.deepcopy(a)
>>> b[0][0] = 100
>>> b
[[100, 2], [2, 3], [3, 4]]
>>> a
[[1, 2], [2, 3], [3, 4]]
>>>
リストのスライス代入

スライス代入によりサイズの変更、削除が自由。

>>> a = [0,1,2,3,4,5,6,7,8,9]
a[2:4] = []
>>> a
[0, 1, 4, 5, 6, 7, 8, 9]
複数同時代入

スワップにも利用可能

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

4. その他の制御フローツール

elif

else ifをelifと省略して書ける。switch文が無いので代わりに使う。

for

C言語のforみたいなのは無くて、foreachのようなforがある。

>>> for a in [1,2,3]:
...     print(a)
...
1
2
3

for (int i = 0; i < 10; i++)
は以下のようにrangeを使う。

>>> for i in range(10):
...     print(i)
...
0
1
2
3
4
5
6
7
8
9

ちなみに、rangeの戻り値がリストになっているかというとそうではない。
rangeクラスのオブジェクトが返る。

>>> print( type([0,1,2]) )
<class 'list'>

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

[https://docs.python.org/ja/3/tutorial/controlflow.html]

range() が返すオブジェクトは、いろいろな点でリストであるかのように振る舞いますが、本当はリストではありません。これは、イテレートした時に望んだ数列の連続した要素を返すオブジェクトです。しかし実際にリストを作るわけではないので、スペースの節約になります。

こういうオブジェクトはイテラブル(iterable: 反復可能)と呼ばれる。
sum()はイテラブルも受け取れるようになっている。(リストも受け取れる)

>>> sum(range(10))
45

>>> sum([0,1,2,3,4,5,6,7,8,9])
45

イテラブルからリストへの変換

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

for文はelse節も持てる。

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

for文のelse句は途中でbreakされると呼ばれない。

>>> for i in range(3):
...     if i > 1:
...             break
...     print(i)
... else:
...     print("test")
...
0
1
pass

pythonはブロックの中を省略できないので、そういう場合はpass文を書いておく。
pass文は何もしない。

>>> a = 1
>>> if a > 0:
...
  File "<stdin>", line 2

    ^
IndentationError: expected an indented block
>>>
>>> if a > 0:
...     pass
...
>>>
>>>
関数
def fib(n):
    """docstringをここに書く"""
    a, b = 0, 1
    for i in range(n):
        a, b = b, a + b
    return b
None

return文の無い関数はNoneを返す。意味がないのでインタプリタは通常出力を抑制する。

def test():
    pass

>>> test()
>>> print(test())
None
>>> type(test())
<class 'NoneType'>
シンボルテーブル

pythonにはブロックスコープがあり、例えば関数内から変数を参照した際には、まず関数内のシンボルテーブルから変数が参照され、次により外側のローカルなシンボルテーブルが参照され、、と続いていく。
ローカルなシンボルテーブルはlocals()で確認可能。

>>> def test():
...     aaa = 1
...     for item in locals().items():
...             print(item[0])
...
>>>
>>> test()
aaa
>>>
デフォルト引数
>>> def test(a = 50):
...     print(a)
...
>>> test()
50
>>> test(2)
2

デフォルト値は最初の一度しか評価されない。
最初の呼び出しでarrが初期化され、それ以降は同じオブジェクトが使い回されるので注意。

>>> def test(arr = []):
...     arr.append("aaa")
...     return arr
...
>>>
>>> test()
['aaa']
>>> test()
['aaa', 'aaa']
>>> test()
['aaa', 'aaa', 'aaa']
キーワード引数

引数名を指定することで、順番を気にせず引数を渡すことが可能

>>> def test(a = 1, b = 2):
...     print(a, b)
...
>>> test()
1 2
>>> test(b = 5)
1 5

*name形式の仮引数があると、タプル型として扱われて実引数の値がそこに入る。

>>> def test(*a):
...     print(type(a), a)
...
>>> test()
<class 'tuple'> ()
>>>
>>> test(1, 2, 3)
<class 'tuple'> (1, 2, 3)
>>>
>>> test(1, 2, 3, a = 1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: test() got an unexpected keyword argument 'a'
>>>
>>>

**name形式の仮引数は辞書型として扱われて、実引数のkey, valueが入る。

>>> def test(**a):
...     print(type(a), a)
...
>>> test()
<class 'dict'> {}
>>>
>>> test(b = 1, c = 2)
<class 'dict'> {'b': 1, 'c': 2}
>>>
>>>
>>> test(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: test() takes 0 positional arguments but 1 was given
>>>

*name形式、**name形式は混在可能。

>>> def test(*a, **b):
...     print(type(a), a, type(b), b)
...
>>> test()
<class 'tuple'> () <class 'dict'> {}
>>>
>>> test(1,2,3)
<class 'tuple'> (1, 2, 3) <class 'dict'> {}
>>>
>>> test(a = 1)
<class 'tuple'> () <class 'dict'> {'a': 1}
>>>
>>> test(1,2,3,a = 1)
<class 'tuple'> (1, 2, 3) <class 'dict'> {'a': 1}
>>>
特殊な仮引数「/」「*」
def test(a, /, b, *, c):

とした場合、
a: 第一引数としてのみ渡せる。(位置専用引数)
b: 第二引数として渡すか、キーワード引数として渡す。
c: キーワード引数としてのみ渡せる。(キーワード専用引数)
という意味になる。

>>> def test(a, /, b, *, c):
...     print(a, b, c)
...
>>> test(1, 2, 3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: test() takes 2 positional arguments but 3 were given
>>>
>>> test(1, 2, c = 3)
1 2 3
>>>
>>> test(1, c = 3, b = 2)
1 2 3
>>>
>>> test(c = 3, b = 2, a = 1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: test() got some positional-only arguments passed as keyword arguments: 'a'

以下のように、キーワード専用のみの引数も定義可能

>>> def test(*, a):
...     print(a)
...
>>> test()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: test() missing 1 required keyword-only argument: 'a'
>>>
>>> test(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: test() takes 0 positional arguments but 1 was given
>>>
>>> test(a = 1)
1
>>>
任意引数リスト

必須パラメータと任意パラメータを以下のように組み合わせることがある。
複数の引数を入力した場合は、paramsにタプル型として渡される。

>>> def test(a, *params):
...     print(a, params)
...
>>> test()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: test() missing 1 required positional argument: 'a'
>>> test(1)
1 ()
>>> test(1, 2)
1 (2,)
>>> test(1, 2, 3)
1 (2, 3)
>>>

任意引数リストの後に定義した仮引数はキーワード専用変数になる。

>>> def test(*params, a):
...     print(params, a)
...
>>> test()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: test() missing 1 required keyword-only argument: 'a'
>>> test(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: test() missing 1 required keyword-only argument: 'a'
>>> test(a = 0)
() 0
>>> test(1, a = 0)
(1,) 0
>>> test(1, 2, a = 0)
(1, 2) 0
アンパック

リストをアンパックすることにより、要素を引数として利用可能

>>> def test(a, b, c):
...     print(a, b, c)
...
>>> l = [1, 2, 3]
>>> test(l)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: test() missing 2 required positional arguments: 'b' and 'c'
>>>
>>> test(*l)
1 2 3
>>>

辞書もアンパックすることでキーワード引数として利用可能

>>> def test(a, b, c):
...     print(a, b, c)
...
>>> d = {"a": 10, "b": 20, "c": 30}
>>>
>>> test(d)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: test() missing 2 required positional arguments: 'b' and 'c'
>>>
>>>
>>> test(**d)
10 20 30
引数まとめ
  • 引数の名前を知らせる必要がないなら位置専用引数を使う。
  • 引数の名前に意味があるなら順番を覚える必要のないキーワード引数が便利。
  • APIでは名前を変更した際に動かなくなると困るので位置専用引数を使う。
ラムダ式
>>> a = lambda n: n + 1
>>> a
<function <lambda> at 0x104fc3af0>
>>> a(1)
2
>>>
ドキュメンテーション文字列
>>> def test(n):
...     """1行目は簡潔にまとめた説明を書く。
...
...     2行目は空けて、3行目から呼び出しの規約、副作用について書く。
...     """
...     return n + 1
...
>>> test(1)
2
>>> test.__doc__
'1行目は簡潔にまとめた説明を書く。\n\n\t2行目は空けて、3行目から呼び出しの規約、副作用について書く。\n\t'
>>>
アノテーション

ドキュメントとして戻り値の型を知らせることが可能。動作には影響しない。

>>> def test()->str:
...     print("test")
...
>>> test.__annotations__
{'return': <class 'str'>}
コーディング規約

基本PEP8に従う。PEPは海外でも「ペップ」と読まれているらしい。
[https://pep8-ja.readthedocs.io/ja/latest/]

5. データ構造

List

初期化
>>> a = []
>>> type(a)
<class 'list'>
要素の追加: append(x)

マージはされない。

>>> a = []
>>> a.append(1)
>>> a
[1]
>>> a.append([2,3,4])
>>> a
[1, [2, 3, 4]]
要素の拡張: extend(iterable)
>>> a
[1, 2, 3]
>>> a.extend([4,5,6])
>>> a
[1, 2, 3, 4, 5, 6]
>>>
要素の挿入: insert(i, x)
>>> a = [5, 6, 7]
>>> a.insert(2, 100)
>>> a
[5, 6, 100, 7]
>>>
要素の削除: remove(x)

値で検索して最初の要素を削除

>>> a = [100, 200, 300]
>>> a.remove(200)
>>> a
[100, 300]
>>>
要素の取り出し: pop([i])

指定しなければ末尾、指定されればそのインデックスの要素を取り出す。

>>> a = [100, 200, 300, 400]
>>> a.pop()
400
>>> a
[100, 200, 300]
>>> a.pop(1)
200
>>> a
[100, 300]
要素の全削除: clear()
>>> a = [1, 2, 3, 4, 5]
>>> a.clear()
>>> a
[]
>>>
要素の検索: index(x, [start, end])
>>> a = [1, 3, 5, 7, 9]
>>> a.index(5)
2
>>> a.index(5, 0, 4)
2
>>> a.index(5, 0, 3)
2
>>> a.index(5, 0, 2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: 5 is not in list
>>>
>>> a.index(5, 1, 4)
2
>>> a.index(5, 2, 4)
2
>>> a.index(5, 3, 4)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: 5 is not in list
要素のカウント: count
>>> a
[5, 6, 6, 7, 7, 7, 8]
>>>
>>> a.count(6)
2
>>> a.count(7)
3
>>>
ソート
>>> a
[4, 2, 6, 6, 5, 4]
>>> a.sort()
>>> a
[2, 4, 4, 5, 6, 6]
>>> a.sort(reverse=True)
>>> a
[6, 6, 5, 4, 4, 2]
キーによるソート
>>> a
[(5, 'asdf'), (2, 'zxcv'), (8, 'bbb')]
>>> a.sort(key = lambda tpl: tpl[1])
>>> a
[(5, 'asdf'), (8, 'bbb'), (2, 'zxcv')]
逆順ソート
>>> a
[6, 6, 5, 4, 4, 2]
>>> a.sort()
>>> a
[2, 4, 4, 5, 6, 6]
>>> a.reverse()
>>>
>>> a
[6, 6, 5, 4, 4, 2]
配列のコピー(shallow)
>>> b = a.copy()
>>> b
[1, 2, 3]
>>> b[0] = 100
>>> b
[100, 2, 3]
>>> a
[1, 2, 3]
>>>
スタック
>>> a = [1,2,3]
>>> a.pop()
3
>>> a
[1, 2]
>>>
キュー

リストの終端に対する操作は速いが、先頭に対しての操作は遅い。
例えば、先頭から1つ取り出すと、要素を全てずらす必要がある。
キューを利用したい場合、リストではなくキューの操作に最適化されたdequeを利用するのがよい。

double-ended queue (両端キュー)

>>> a = deque([1,2,3])
>>> a
deque([1, 2, 3])
>>> a.popleft()
1
>>> a.popleft()
2
>>> a
deque([3])
>>> a.append(5)
>>> a
deque([3, 5])
リスト内包表記
>>> a = [ i * 2 for i in range(10) ]
>>> a
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18] 

2重ループ

>>> [(i,j) for i in range(3) for j in range(3)]
[(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1), (2, 2)]
del

関数ではなく文である理由:

[https://ja.stackoverflow.com/questions/23100/python3-%E3%81%A7-del%E6%96%87%E3%81%8C%E6%8E%92%E9%99%A4%E3%81%95%E3%82%8C%E3%81%AA%E3%81%8B%E3%81%A3%E3%81%9F%E3%81%AE%E3%81%AF%E3%81%AA%E3%81%9C%E3%81%A7%E3%81%99%E3%81%8B]

>>> a = [1,2,3]
>>> a
[1, 2, 3]
>>> del a[0]
>>> a
[2, 3]
タプル
>>> a = 1,2,3
>>> a
(1, 2, 3)
>>> type(a)
<class 'tuple'>
>>> a[2]
3
>>> a = (1,2,3)
>>> a
(1, 2, 3)
>>> type(a)
<class 'tuple'>


>>> a = ()
>>> a
()
>>> type(a)
<class 'tuple'>
>>>
>>> a = 1,
>>> a
(1,)
>>> type(a)
<class 'tuple'>
>>>
>>> len(a)
1
集合

宣言


>>> a = {1,2,3}
>>> type(a)
<class 'set'>
>>>
>>>
>>> a = {}
>>> type(a)
<class 'dict'>
>>>

演算

>>> a = {1,2,3,4,5}
>>> b = {4,5,6,7,8}
>>> a + b
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'set' and 'set'
>>>
>>>
>>>
>>> a - b
{1, 2, 3}
>>> a | b
{1, 2, 3, 4, 5, 6, 7, 8}
>>> a &b
{4, 5}
>>>
>>>
>>> a ^ b
{1, 2, 3, 6, 7, 8}
>>>
辞書

マップ、連想配列、と同じもの。

>>> a = {'aaa': 10, 'bbb': 20}
>>> a
{'aaa': 10, 'bbb': 20}
>>> a['aaa']
10

items()メソッドにより、key, value両方が取り出せる。
dict_items型として取り出しているらしい。

>>> for i in a:
...     print(i, a[i])
...
aaa 10
bbb 20
>>>
>>>
>>> for k, v in a.items():
...     print(k, v)
...
aaa 10
bbb 20


>>> a.items()
dict_items([('aaa', 10), ('bbb', 20)])
>>> type(a.items())
<class 'dict_items'>
>>>
zip
>>> for i, j in zip(a, b):
...     print(i, j)
...
1 4
2 5
3 6
配列の要素をユニークに
>>> a = [1, 2, 2, 3, 3, 4]
>>> a
[1, 2, 2, 3, 3, 4]
>>> set(a)
{1, 2, 3, 4}
>>>
in

要素が存在するか調べる。

>>> a
[1, 2, 2, 3, 3, 4]
>>> 3 in a
True
>>> 5 in a
False
セイウチ演算子

「:=」のこと。式中での代入に使う。

>>> f = open("aaa")
>>> while line := f.readline():
...     line
...
'aaa\n'
'bbb\n'
'ccc\n'
>>> while line = f.readline():
  File "<stdin>", line 1
    while line = f.readline():
               ^
SyntaxError: invalid syntax
>>>
シーケンスオブジェクト同士の比較

辞書順で先頭から比較していく。

>>> [1,2,3,4,5] < [1,2,4,5,6]
True

6. モジュール

モジュールの作成

処理をファイルに分割して記述し、別の場所からファイル名 = モジュール名としてimport可能。

$ cat test.py
def aaa():
    print("test func is called.")

>>> import test
>>> dir(test)
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'aaa']
>>> test.aaa()
test func is called.
>>>
モジュールの定義を全てimport

読み込み元の名前空間にシンボルが上書きされるので、確認用途以外では使わない。

$ cat aaa.py

def asdf():
    print("asdf")

def zxcv():
    print("zxcv")

def qwer():
    print("qwer")

>>> from aaa import *
>>> globals()
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, 'asdf': <function asdf at 0x1015f2d30>, 'zxcv': <function zxcv at 0x1015f2ee0>, 'qwer': <function qwer at 0x1015f2f70>}
名前付きでimport
>>> import test as zzzzz
>>> zzzzz.aaa()
test func is called.
モジュールの初期実行

モジュール読み込み時に一度モジュールの中身が実行される。
通常、これを初期化に利用する。

$ cat bbb.py

print("bbb.py is loaded.")

def test():
    print("test is called.")



>>> import bbb
bbb.py is loaded.
>>> bbb.test()
test is called.
>>>
コマンドラインから実行した場合の処理

コマンドラインからスクリプトを実行すると、モジュールの「__name__」が「__main__」になる。

$ cat ccc.py

cat ccc.py

def test():
    print("test is called.")

print(__name__)


>>> import ccc
ccc
>>>

$ python ccc.py
__main__

これを利用し、コマンドライン実行の場合のみ実行される処理が定義可能。

$ cat ccc.py

f = False

def action():
    print("Initialized." if f else "Not initialized.")

if __name__ == "__main__":
    f = True
    action()



$ python ccc.py
Initialized.

>>> import ccc
>>> ccc.action()
Not initialized.
>>>
モジュールの検索パス

モジュールは以下のパスから検索される。

  • ビルトインモジュール
  • 実行対象のスクリプトのディレクトリ
  • カレントディレクトリ
  • PYTHONPATH
モジュールのコンパイル

モジュールの読み込みを高速化するため、コンパイルされているファイルが存在しない場合、読み込み時にモジュールはコンパイルされ、「__pycache__」ディレクトリに出力される。

ls __pycache__
aaa.cpython-39.pyc  ccc.cpython-39.pyc  test.cpython-39.pyc
bbb.cpython-39.pyc  fibo.cpython-39.pyc
標準モジュール

いくつかのモジュールはインタプリタにビルトインされている。
例えば、sysモジュール等。

dir()

引数無し実行。
locals()で得られるリストと同様の模様。

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

>>> sorted(list(locals().keys()))
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'a']

モジュール内の変数、関数などのリスト取得

>>> import aaa
>>> dir(aaa)
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'asdf', 'qwer', 'zxcv']
パッケージ

「__init__.py」が存在するとパッケージとして認識される。

>>> import p.a.aaa
p.__init__ is loaded.
a.__init__ is loaded.
aaa.py is loaded.
>>>
>>> p.a.aaa.test()
aaa.test() is called.
>>>
読み込まれるモジュールの指定

「__init__.py」内の「__all__」にデフォルトで読み込むモジュールを指定可能

$ ls
__init__.py  asdf.py     qwer.py     zxcv.py


$ cat __init__.py

print("b.__init__ is called.")
__all__ = ["qwer", "zxcv"]


>>> from p.b import *
p.__init__ is loaded.
b.__init__ is called.
qwer is loaded
zxcv is loaded
同パッケージ内でのモジュール読み込み

同じパッケージに属するモジュールを読み込む際、

  • 絶対パスでimport
  • 相対パスでimport

の2パターン存在する。

絶対パスでimportする場合

$ cat p/b/test.py
import p.a.aaa

def f():
    p.a.aaa.test()


>>> import p.b.test
p.__init__ is loaded.
b.__init__ is called.
a.__init__ is loaded.
aaa.py is loaded.
>>>
>>> p.b.test.f()
aaa.test() is called.
>>>

相対パスでimportする場合

cat test.py
from ..a import aaa

def f():
    aaa.test()



>>> import p.b.test
p.__init__ is loaded.
b.__init__ is called.
a.__init__ is loaded.
aaa.py is loaded.
>>>
>>> p.b.test.f()
aaa.test() is called.
>>>

7. 入力と出力

変数展開
>>> v = 100
>>> s = f'v: {v}'
>>>
>>> s
'v: 100'
>>> 
>>> f'pi: {math.pi:.3f}'
'pi: 3.142'
>>>
formatメソッド
>>> import math
>>> '{:1.5}'.format(math.pi)
'3.1416'
>>> '{:1.7}'.format(math.pi)
'3.141593'
repr()とstr()

reprはデバッグ用途。

>>> str(datetime.date.today())
'2021-02-10'
>>>
>>> repr(datetime.date.today())
'datetime.date(2021, 2, 10)'
>>>
str.format()とvars()の組み合わせ

vars()はローカルな変数の一覧を返す。
以下のように利用すると、format()の引数を入力する手間が省けてデバッグ時に便利。

>>> format = 'a = {a}, v = {v}'
>>> a = 5
>>> v = 10
>>>
>>> fmt.format(**vars())
'a = 5, v = 10'
出力文字列のセンタリング

右寄せはrjust(10), 左寄せはrjust(10)。
引数10は桁数。

>>> for i in range(10):
...     print(repr(i**5).center(10))
...
    0
    1
    32
   243
   1024
   3125
   7776
  16807
  32768
  59049
ゼロ埋め
>>> '5'.zfill(5)
'00005'
古いフォーマット指定
>>> 'pi: %.3f' % math.pi
'pi: 3.142'
ファイル書き込み・読み込み
>>> f = open("a", "w")
>>> f.write("hello world.")
12
>>> f.close()
>>>
>>>
>>> f = open("a")
>>> f.readline()
'hello world.'
>>>
>>> f.seek(0)
0
>>> f.readline()
'hello world.'
>>>
with文によるclose()の省略
>>> with open("a") as f:
...     f.readline()
...
'hello world.'
>>>
読み込む文字数の指定
>>> with open("a") as f:
...     f.read(1)
...
'h'
1行ずつ高速に読み込む
$ cat aaa
aaa
bbb
ccc

>>> f = open("aaa")
>>> for line in f:
...     line
...
'aaa\n'
'bbb\n'
'ccc\n'
>>>
全ての行を一度に読み込む

readline()使用

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

list()使用

>>> with open("aaa") as f:
...     list(f)
...
['aaa\n', 'bbb\n', 'ccc\n']
jsonの読み込み
$ cat a.json
{
    "aaa": 5,
    "bbb": [1, 2, 3],
    "ccc": "test"
}


>>> with open("a.json") as f:
...     json.load(f)
...
{'aaa': 5, 'bbb': [1, 2, 3], 'ccc': 'test'}

8. エラーと例外

構文エラー

SyntaxError: 構文エラー

>>> if aaa
  File "<stdin>", line 1
    if aaa
          ^
SyntaxError: invalid syntax
>>>
例外のtry, catch
>>> try:
...     aaa
... except NameError:
...     print("Name Error occured.")
...
Name Error occured.
例外が起きなかった場合の処理 (try, else)
>>> try:
...     True
... except NameError:
...     print("name error.")
... else:
...     print("no problem.")
...
True
no problem.
>>>
例外の送出 (raise)

raiseで送出できるのはExceptionクラスを継承した例外クラスのみ。

>>> try:
...     raise Exception("aaa")
... except Exception as e:
...     e
...     type(e)
...     e.args
...
Exception('aaa')
<class 'Exception'>
('aaa',)
>>>

raiseを再度実行することで、再送出可能

>>> try:
...     raise Exception("aaa")
... except Exception as e:
...     e
...     raise
...
Exception('aaa')
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
Exception: aaa
例外の連鎖 (raise, from)
>>> try:
...     raise IOError
... except Exception as e:
...     raise RuntimeError from e
...
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
OSError

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

Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
RuntimeError
>>>
例外クラスの自作

Exceptionから派生したクラスを作成可能

>>> class MyError(Exception):
...     pass
...
>>>
>>> raise MyError("test")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
__main__.MyError: test
必ず実行されるクリーンアップ動作 (Finally節)

with文を利用したファイル操作等、問題発生時にファイルがcloseされるようにクリーンアップ動作が設定されているものがある。
例外処理の際、このようなクリーンアップ動作をFinally節により指定可能。

>>> try:
...     True
... except NameError:
...     print("name error.")
... else:
...     print("no problem")
... finally:
...     print("done.")
...
True
no problem
done.
>>>

9. クラス

  • クラスはデータと機能を組み合わせる方法
  • 他の言語より最小限の構文でクラスを作成できる
  • オブジェクト指向プログラミングの標準的な機能を提供する。
  • 複数の基底クラスを持つことが可能
  • 基本的にクラスメンバはpublicで、virtual。よってオーバーライド可能。
  • c++と異なり、組み込み型についても拡張可能。
nonlocal

Pythonでは、外側のスコープの変数は基本読み取り専用。
a()の中からvalの読み取りは可能。

>>> def test():
...     def a():
...             print(val)
...     val = 1
...     a()
...
>>> test()
1

しかし、val = 3と書き換えようとするとエラー。

>>> def test():
...     def a():
...             print(val)
...             val = 3
...     val = 1
...     a()
...
>>> test()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 6, in test
  File "<stdin>", line 3, in a
UnboundLocalError: local variable 'val' referenced before assignment
>>>

これに対し、nonlocal文を使うことで書き込みできるようになる。

def test():
    def a():
        nonlocal val
        print(val)
        val = 3

    val = 1
    a()

>>> test()
>>> 1
global

ローカル変数のグローバルスコープへの束縛。

def test():
    global a
    a = "asdf"

test()
a
class

クラス定義はif文内にも記述可能

>>> f = 1
>>> if f == 1:
...     class A:
...             pass
... else:
...     class B:
...             pass
...
>>> A()
<__main__.A object at 0x102fbdb20>
>>> B()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'B' is not defined

class定義の終端を抜けるとクラスオブジェクトが生成される。
別の言語のような宣言ではなくクラスオブジェクトの生成であり、type型のインスタンスが作成される。

>>> class A:
...     pass
...
>>> A
<class '__main__.A'>
属性参照

クラスのメンバ変数、メンバ関数のことを属性と呼んでいる。
属性には「.」でアクセスする。

>>> class A:
...     a = 1
...     def asdf():
...             print("asdf is called")
...
>>> A.a
1
>>> A.asdf()
asdf is called
クラスのインスタンス化

pythonにnew演算子は無く、クラスオブジェクトをcallすることでインスタンスが作成される。

>>> class A:
...     pass
...
>>> A()
<__main__.A object at 0x1013129d0>
init

クラスのインスタンス化時に「__init__」メソッドが呼ばれる。
通常、初期化はこの中で行う。

>>> class A:
...     def __init__(self):
...             print("init is called.")
...
>>>
>>> A()
init is called.
<__main__.A object at 0x10149d430>
メソッドオブジェクト

メソッドの第一引数にはインスタンスオブジェクトが渡される。

>>> class A:
...     def test(self):
...             print(self)
...
>>> a = A()
>>> a.test()
<__main__.A object at 0x1013d4c40>
>>>
>>> A.test("aa")
aa
>>> A.test()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: test() missing 1 required positional argument: 'self'
>>>

インスタンスオブジェクトの変数名は別に「self」でなくてもよい。
が、ツール等でこの文字列を当てにしている場合があるので注意。

>>> class A:
...     def __init__(a):
...             print(a)
...
>>> a = A()
<__main__.A object at 0x1013b9b50>
メンバ変数
>>> class A:
...     def __init__(self):
...             self.val = "default"
...     def set(self, val):
...             self.val = val
...
>>> a = A()
>>> a.val
'default'
>>>
>>> a.set(10)
>>> a.val
10
>>>
メソッドが参照するスコープ

メソッド内から参照するスコープは基本グローバルスコープ。
データ属性を優先的に参照するなどは無い。

>>> a = 1
>>> class A:
...     a = 2
...     def f(self):
...             print(a)
...             print(self.a)

>>> b = A()
>>> b.f()
1
2
プライベート変数について

クラスのメンバ(データ属性)はプライベートにできない。これはPythonにおいてクラスを純粋な抽象データ型として使うことができないことを意味する。
あらゆる属性をプライベートにできないので、クラスの利用者はいくらでも拡張してしまえる。そこで、Pythonではアンダースコア始まりのメンバをプライベートとして扱うことが慣習となっている。

>>> class A:
...     _val = 1
...     def f(self):
...             print(self._val)
...
>>> a = A()
>>> a.f()
1
>>>
>>> a._val
1
>>>

あくまで慣習なので、上記の例のように「a._val」とアクセスできるが、推奨はされない。

ネームマングリング (name mangling)

アンダースコア2つで始まる属性はクラス名を頭に付けないと参照できなくなる。
これをネームマングリング機構と呼ぶ。
主に親クラスと名前が衝突しないようにするために利用される。

>>> class A:
...     __val = 1
...     def f(self):
...             print(self.__val)
...
>>>
>>> a = A()
>>> a.__val
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'A' object has no attribute '__val'
>>>
>>> a._A__val
1
>>>
>>> a.f()
1
>>>
継承

以下のようにしてクラスを継承する。

>>> class A:
...     a = 1
...
>>>
>>> class B(A):
...     def f(self):
...             print(self.a)
...
>>>
>>> b = B()
>>> b.f()
1
>>>

属性は全てvirtualなので、派生クラス側で上書きできる。

>>> class A:
...     def f(self):
...             print(self.val)
...
>>>
>>> class B(A):
...     val = 5
...
>>> b = B()
>>>
>>> b.f()
5
多重継承

以下のようにしてクラスを多重継承する。
メソッドの検索順は、多重継承時に記述した順番に依存する。

>>> class A:
...     def f(self):
...             print("A.f is called.")
...
>>> class B:
...     def f(self):
...             print("B.f is called.")
...
>>> class C(A, b):
...     pass
...
>>> c = C()
>>> c.f()
A.f is called.
>>>
>>> class C2(B, A):
...     pass
...
>>>
>>> c2 = C2()
>>> c2.f()
B.f is called.
構造体

Cのような構造体は存在しないが、以下のように空のクラスで代用することがある。

>>>
>>> class A:
...     pass
...
>>> a = A()
>>> a.name = "test"
>>> a.val = 5
>>>
イテレータ

Pythonの各コンテナオブジェクトはイテレータが実装されているため、for文によりループで各要素にアクセス出来て便利。
イテレータは以下のように自作クラスに実装できる。

$ cat iterator.py

class A:
    def __init__(self, data):
        self.data = data
        self.index = 0

    def __iter__(self):
        return self

    def __next__(self):
        if len(self.data) == self.index:
            raise StopIteration
        self.index += 1
        return self.data[self.index - 1]


>>> a = A("asdf")
>>> for s in a:
...     s
...
'a'
's'
'd'
'f'
ジェネレータ (generator)

ジェネレータを利用することでイテレータを自動で作成できる。
これにより「iter」、「next」を実装する手間が省ける。

$ cat generator.py

class A:
    def __init__(self, data):
        self.data = data
        self.index = 0

    def sequence(self):
        for i in range(len(self.data)):
            yield self.data[i]


>>> a = A("asdf")
>>> for s in a.sequence():
...     s
...
'a'
's'
'd'
'f'
ジェネレータ式

リスト内包表記を括弧で囲ったものがジェネレータ式。

>>> g = (i * i for i in range(10))
>>>
>>> g
<generator object <genexpr> at 0x10522d7b0>
>>> 
>>> list(g)
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>>
>>> list(g)
[]
>>> 
>>> g = (i * i for i in range(10))
>>> sum(g)
285

10. 標準ライブラリミニツアー

os: OSとの対話

「from os import * 」と読み込むとopen()とos.open()が被るので「import os」と読み込む。

カレントディレクトリ取得 (os.getcwd())
>>> import os
>>> os.getcwd()
'/private/tmp'
ディレクトリの移動 (os.chdir())
>>> os.chdir("/var")
>>> os.getcwd()
'/private/var'
>>>
シェルコマンド実行
>>> os.system("date")
2021 2月22日 月曜日 18時19分04秒 JST

shutil: ファイル操作

ファイルコピー (shutil.copyfile)
>>> shutil.copyfile("aaa", "aaa2")
'aaa2'
>>>

glob: ファイルリスト取得

特定の拡張子のファイル一覧取得 (glob.glob())
>>> glob.glob("*.py")
['fibo.py', 'ccc.py', 'bbb.py', 'aaa.py', 'test.py', 'generator.py', 'iterator.py', 'sys.py']

sys: システムパラメータ取得

コマンドライン引数 (sys.argv)
$ python -i aaa.py
>>> import sys
>>> sys.argv
['aaa.py']
標準エラー出力 (sys.stderr.write())
>>> sys.stderr.write("test")
4
test>>>
>>>
スクリプトの終了 (sys.exit())
>>> sys.exit()
$

argparse: コマンドライン引数処理

プログラム名取得 (ArgumentParser.prog)
>>> parser = argparse.ArgumentParser()
>>> parser.prog
'aaa.py'
>>>

re: 文字列のパターンマッチング

正規表現 (re.findall)
>>> import re
>>> re.findall('asdf[a-z]*', "1234asdfwer78989")
['asdfwer']

random: 乱数に基づいた要素選択

ランダムに要素を選択 (random.choice())
>>> import random
>>> random.choice([1,2,3,4,5])
3
乱数の生成 (random.random())
>>> import random
>>> random.random()
0.31780501360951163

statistics: 統計

平均 (statistics.mean())
>>> import statistics
>>> statistics.mean([1,3,5,7,9,11])
6
標本分散 (statistics.pvariance())
>>> import statistics
>>> statistics.pvariance([1,3,5,7,9,11])
11.666666666666666

urllib: インターネットアクセス

GETリクエスト (urllib.urlopen)
>>> for line in urlopen("https://example.com"):
...     line
...
b'<!doctype html>\n'
b'<html>\n'
b'<head>\n'
b'    <title>Example Domain</title>\n'
b'\n'
b'    <meta charset="utf-8" />\n'
b'    <meta http-equiv="Content-type" content="text/html; charset=utf-8" />\n'
b'    <meta name="viewport" content="width=device-width, initial-scale=1" />\n'
b'    <style type="text/css">\n'
b'    body {\n'
...
...

datetime: 日時の操作

現在の日時取得
>>> import datetime
>>> datetime.date.today()
datetime.date(2021, 2, 22)

gzip: データ圧縮

文字列の圧縮 (zlib.compress())
import zlib
>>> s = b'This domain is for use in illustrative examples in documents. You may use this domain in literature without prior coordination or asking for permission.'

>>> len(s)
152
>>> len(zlib.compress(s))
113

timeit: 実行時間計測

実行時間の計測 (timeit.timeit())
>>> timeit.timeit("[i * i for i in range(10)]")
0.4927712500000325
>>>
>>>
>>> timeit.timeit("[i * i for i in range(100)]")
3.143982625000035
>>>

doctest: テスト

docstringに埋め込んだテストをdoctestモジュールで実行することが可能。

$ cat mytest.py
def mytest1(a, b):
    """Calc sum of a and b.

    >>> print(mytest1(1, 2))
    4

    """
    return a + b


if __name__ == "__main__":
    import doctest
    doctest.testmod()


$ python mytest.py
**********************************************************************
File "/private/tmp/mytest.py", line 4, in __main__.mytest1
Failed example:
    print(mytest1(1, 2))
Expected:
    4
Got:
    3
**********************************************************************
1 items had failures:
   1 of   1 in __main__.mytest1
***Test Failed*** 1 failures.

11. 標準ライブラリミニツアー --- その 2

reprlib: オブジェクトの省略表現

オブジェクトの文字列表現 (repr())

eval()によってオブジェクトに戻せるような文字列に変換する。

>>> import datetime
>>> a = datetime.datetime.today()
>>> str(a)
'2021-02-23 23:52:09.178283'
>>> repr(a)
'datetime.datetime(2021, 2, 23, 23, 52, 9, 178283)'
>>>
>>> b = eval( repr(a) )
>>> b
datetime.datetime(2021, 2, 23, 23, 52, 9, 178283)
>>>
オブジェクトの省略表現 (reprlib.repr())

デバッグ用途等のため、オブジェクトをrepr()の短縮版に変換する。

>>> import reprlib
>>> a = datetime.datetime.today()
>>> repr(a)
'datetime.datetime(2021, 2, 23, 23, 52, 9, 178283)'
>>>
>>> reprlib.repr(a)
'datetime.date...52, 9, 178283)'
>>>

pprint: オブジェクトの表示制御

改行表示 (pprint.pprint())
>>> import pprint
>>> a = {"a": 1, "b": [1,2,3], "c": "zxcv"}
>>> a
{'a': 1, 'b': [1, 2, 3], 'c': 'zxcv'}
>>>
>>> pprint.pprint(a, width=5)
{'a': 1,
 'b': [1,
       2,
       3],
 'c': 'zxcv'}
>>>

textwrap: テキストの折り返しと詰め込み

文章の表示幅制限 (textwrap.fill())
>>> a = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque egestas ex a orci fringilla finibus. "
>>> print(textwrap.fill(a, width=20))
Lorem ipsum dolor
sit amet,
consectetur
adipiscing elit.
Quisque egestas ex a
orci fringilla
finibus.

locale: 国際化

通貨の表示 (locale.currency())
>>> locale.setlocale(locale.LC_ALL, '')
'ja_JP.UTF-8'
>>>
>>> locale.currency(12345678)
'¥12345678'
>>>
>>> locale.currency(12345678, grouping=True)
'¥12,345,678'

string: 文字列テンプレート

変数展開 (string.substitute())
>>> from string import Template
>>> t = Template('a: ${a}, b: ${b}')
>>> t.substitute(a = "aaa", b = "bbb")
'a: aaa, b: bbb'

struct: バイナリデータ処理

数値 - バイナリ間の変換 (struct.pack(), struct.unpack())
>>> a = struct.pack('b', 127)
>>> a
b'\x7f'
>>> type(a)
<class 'bytes'>
>>> struct.unpack('b', a)
(127,)

threading: マルチスレッド


##### スレッド作成 (threding.Thread())

>>> import threading
>>> import time
>>>
>>> class A:
...     val = 0
...     def test(self):
...             time.sleep(3)
...             self.val = 100
>>>
>>> th = threading.Thread(target = a.test)
>>> th.start()
>>>
>>> a.val
0
>>> a.val
100

logging: ログ


##### 標準エラー出力 (logging.error())

>>> import logging
>>> logging.error("test")
ERROR:root:test

weakref: 弱参照


pythonのGCはリファレンスカウンタ方式なので、delによって削除を行っても参照されている限りメモリが開放されない。
weakrefを利用すると、参照先が削除された際にメモリが開放されるような弱参照の辞書を作成できる。


弱参照辞書 (weakref.WeakValueDictionary())
>>> a = User("asdf")
>>> a
<__main__.User object at 0x103649b20>
>>> b = {"user": a}
>>> b
{'user': <__main__.User object at 0x103649b20>}
>>> del a
>>> b["user"]
<__main__.User object at 0x103649b20>
>>> b["user"].name
'asdf'


>>> import weakref
>>> a = User("asdf")
>>> a
<__main__.User object at 0x1035db7c0>
>>> b = weakref.WeakValueDictionary({"user": a})
>>> b
<WeakValueDictionary at 0x10366f4f0>
>>> b["user"].name
'asdf'
>>> del a
>>>
>>> b["user"]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/opt/homebrew/Cellar/python@3.9/3.9.1_6/Frameworks/Python.framework/Versions/3.9/lib/python3.9/weakref.py", line 134, in __getitem__
    o = self.data[key]()
KeyError: 'user'
>>>

array: 配列

arrayはlist型と違って指定した型のデータしか受け入れない代わりに、コンパクトな配列を作成できる。

バイト型の配列 (array.array('b', ...))
>>> import sys
>>> import array
>>>
>>> a = [1,2,3]
>>> b = array.array('b',[1,2,3])
>>>
>>> sys.getsizeof(a)
120
>>> sys.getsizeof(b)
67

decimal: 浮動小数演算

浮動小数点誤差が出ると困る場合、decimalモジュールで精度を指定して演算を行う。

>>> 0.1 + 0.2
0.30000000000000004
>>> decimal.Decimal(0.1) + decimal.Decimal(0.2)
Decimal('0.3000000000000000166533453694')
>>> decimal.getcontext().prec = 2
>>> decimal.Decimal(0.1) + decimal.Decimal(0.2)
Decimal('0.30')

12. 仮想環境とパッケージ

pip install等でモジュールをインストールする環境を分けたい場合、venvを利用してディレクトリ単位の仮想環境を作成する。

$ py -m venv my-project
$ cat my-project/pyvenv.cfg
home = /opt/homebrew/bin
include-system-site-packages = false
version = 3.9.1

13. さあ何を?

リンク集。

14. 対話入力編集と履歴置換

対話シェルの種類

bpython

入力補完とドキュメントの表示が強力な対話シェル。

[https://www.bpython-interpreter.org/]

$ brew install bpython

対話シェルでの実行履歴

デフォルトで以下のファイルに保存される。

$ cat ~/.python_history

15. 浮動小数点演算、その問題と制限

丸め誤差

浮動小数点の丸め誤差に注意。
round()で適宜計算結果を丸める等の対策が必要。

>>> 0.1 + 0.1 == 0.2
True
>>> 0.1 + 0.1 + 0.1 == 0.3
False
>>>
>>> round(0.1 + 0.1 + 0.1, 2) == 0.3
True

16. 付録

対話モード起動時に読み込むスクリプト

環境変数「PYTHONSTARTUP」に指定されたパスのスクリプトは対話モードの開始時に読み込まれる。
よく利用するモジュールのimportや自作のちょっとしたモジュールはここに書いておくと便利。

# ~/.zshrc
...
export PYTHONSTARTUP=~/.python_startup.py
...


# ~/.python_startup.py
def myfunc():
    print("test")


$ python
Python 3.9.2 (default, Feb 19 2021, 06:54:56)
[Clang 12.0.0 (clang-1200.0.32.29)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>
>>> myfunc()
test
>>>
実行可能なPythonスクリプト

スクリプトにシバンを書き、実行権限を付与しておくと、直接実行できて便利。

$ cat test.py
#!/usr/bin/env python3

def run():
    print("run func is called.")

if __name__ == "__main__":
    run()



$ chmod a+x test.py
$ ./test.py
run func is called.
2
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
5