わかったつもりのPython その4(最後)

python

 前回の続き。オライリーの読解です。O’Reilly Python 文法詳解

 普通に書いてたら理解できる文法の「きほんのき」は省いて「へぇ~」と思った所をピックアップしていきます。前回の続きで7~8章。最後です。細かい解説はしていません。「へぇ~」と思ったらそのキーワードでググるか本買ってください。

データ型とプロトコル

 何か色々雑多な章です。

ファイル入出力

 Cでいうところのfopen()。引数の意味も似ています。tだのbだの、wだのrだの。「ふ~ん」な感じです。このあたりは割愛

行単位の読み込み

 やはりinを使ったfor文で書くのがおしゃれ。

for line in open('text.txt'):
    print(line)

ファイルのクローズ

 とはいえやっぱりwith文を使うのがもっとおしゃれ。

 closeを意識しないでよいです。このwith文を使えるクラスに関してはのちにまた詳細出てきます。

with open('test.txt') as file:
    for line in file:
        print(line)

 try ~finallyでもクローズ忘れを防げます。

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

 オブジェクトをprint()とかした時に表示される文字列を返してくれる関数がstr(obj)です。これともう一つ情報量が多い文字列生成の関数としてrepr(obj)があります。デバッグ上使いやすい感じの出力をしてくれます。

import numpy as np

a = np.array([1,2,3,4,5])

print(a)
print(str(a))
print(repr(a))

 print(a)とprint(str(a))は同じ意味です。これを実行すると、

[1 2 3 4 5]
[1 2 3 4 5]
array([1, 2, 3, 4, 5])

 と出力されます。repr(a)の方が少し親切です。str(a)だとリストだかわからないし、コードにコピペするにしてもカンマを間に入れまくらないといけませんが、repr(a)の方だと、コードへのコピペが簡単。

ascii()

 おまけとして、ascii()っていう文字列生成関数もあります。ASCII文字であれば関係ないのですが、2バイト文字とか使った時の表示を変えられます。あまり使うことはなさそう…。

a = ["1","2","3","4","5"]

print(a)
print(ascii(a))

 こいつを実行すると

['1', '2', '3', '4', '5']
['\uff11', '\uff12', '\uff13', '\uff14', '\uff15']

 こうなります。

文字列表現のカスタマイズ

 このrepr()関数ですが、自前のクラス用にカスタマイズできます。メソッドとして__repr__(self)を定義してやるとよいそうです。

対話コンソールでのrepr()

 コンソールで表示される計算結果は、strではなくrepr()の方が表示されています。へぇ~

イテレータ

 for xx in yy: で書けるあいつです。連続する一連のデータへのアクセスを提供するオブジェクトで、コンテナオブジェクトからの要素の取り出しや、ファイルアクセス、など繰り返し処理でよく使われます。

 イテレータのクラスは以下のメソッドを持っているそうです。

iterator.__iter__(self)
iterator.__next__(self)

 確かに必要そう。

イテラブルなオブジェクト

 イテレータを返すインターフェースを持つオブジェクトのことをイテラブルなオブジェクトと呼ぶそうです。

 イテレータを返すために、

iterable.__iter__(self)
iterable.__getitem__(self)

 のどちらかあるいは両方を持っているそうです。リストはイテラブルなオブジェクトなので__iter__を持っています。__iter__で返ってきたイテレータでは__next__関数を使って値が取れます。iter()って関数でもイテレータを取り出せます。

 何ぞややこしいですが、コードにすると、

a = [1,2,3,4]

b = iter(a) # aはイテラブルなオブジェクトで、bはイテレータ
c = a.__iter__() # これも同じ意味、cはイテレータ

print(next(b, -1))  # 第二引数は空の時返す値
print(next(b, -1))
print(b.__next__())
print(b.__next__())
print(next(b, -1))  # ここはもう空なので -1

print(next(c))  # cはbとまるで同じ
print(next(c))
print(c.__next__())
print(c.__next__())
print(next(c))  # ここは空なので例外が起こる

リバースイテレータ

 逆順アクセスできるイテレータもあるようです。

iterable.__reversed__(self)

 まぁこんなのもあるんだ。程度で。

ジェネレータ

 イテレータを作る関数という理解でよいと思います。return の代わりにyieldを使うととらえるとなんとなく理解できます。繰り返す都度次のyiedまで進み、値を返すと思っていれば間違いなさそうです。

 yield は日本語にすると、生む、もたらす です。

def a():
    print("yield ", 1)
    yield 1
    print("yield ", 2)
    yield "2"
    print("yield ", 3)
    yield [3,3,3]
    print("yield ", 4)
    yield (4,4,4,4)

b = a()

for i in b:
    print("for ", i)

 この例では上から順に実行呼び出されて、結果として

yield  1
for  1
yield  2
for  2
yield  3
for  [3, 3, 3]
yield  4
for  (4, 4, 4, 4)

 と4回ループします。これが基本の形になります。

ジェネレータのメソッド

 この章ではgeneratorのメソッドに関して記述があります。ジェネレータに対して値や例外を送れるようです。

 yieldは必ずしもreturn の意味だけでなく、値を受け取る際にも使われるようです。

def a():
    print("yield", 1)
    sent = yield 1
    print("yield : ", sent , 2)
    sent = yield "2"
    print("yield : ", sent , 3)
    sent = yield [3,3,3]
    print("yield : ", sent , 4)
    yield (4,4,4,4)
    
b = a()

next(b)
b.send("22")
b.send("33")
b.send("44")

 この例では、send関数でイテレーションが開始されているオブジェクトに対して、文字列を送信しています。その受け取り先はyieldです。実行すると、

yield 1
yield :  22 2
yield :  33 3
yield :  44 4

 となり、yieldで値を返すだけでなく、次のイテレーションで受け取りにも使われています。

サブジェネレータ

 だんだんマニアックになってきます。ret = yield form イテレータ という文法が存在して、これを使うとジェネレータを分解できる。ということまで覚えておく程度で、あとは使うときに調べましょう。

ジェネレータ式

 これも、(式 for 変数名 in イテラブルオブジェクト) のフォーマットがあって、お手軽にジェネレータが作れる。というとこまで覚えておいて、あとは使うときに調べましょう。

コンテキストマネージャ

 これは上述のfileオブジェクトで使われていたwith文が使える、自動の初期化終了処理が使えるオブジェクトのことです。このオブジェクトでは、

__enter__(self)
__exit__(self, exc_type, exc_value, traceback)

 の関数が定義されている必要があります。

class a:
    b = 0
    def __enter__(self):
        return self
    def __exit__(self, exc_type, exc_valaue, traceback):
        print("exit[", self.b, "]")
    def inc(self):
        self.b += 1
    
with a() as b:
    b.inc()
    b.inc()
    b.inc()

 これを実行すると

exit[ 3 ]

 です。contextlib.contextmanager()を使うことで、ジェネレータ関数をデコレータとして使う事ができるようです。ん~。まだ出くわしたことがないし、だいぶマニアックなのでこれも詳細保留

ディスクリプタ

 インスタンスから属性の値を取得・設定する際に関数に差し替える機能がこのディスクリプタです。

__get__(self, obj, objtype=None)
__set__(self, obj, value)
__delete__(self, obj)

 の関数があると

class a:
    def __get__(self, obj, objtype=None):
        return "a"
    def __set__(self, obj, value):
        print("set", value)

class b:
    c = a()
    d = a()

e = b()
print(e.c)
print(e.d)
print(b().c)

e.c = 10

 こんなことができちゃいます。class bの属性 c, dにアクセスすると、class a の __get__メソッドが呼ばれます。属性にアクセス(値の取得、値の設定)したのにこの動きには

a
a
a
set 10

 値にアクセスしたときに何ぞ裏で処理(関数)が動いています。気味悪い…。

 ほかにもディスクリプタには機能があるようですが、いまいちピンとこなかったのでここでは保留。詳細は書籍を読んでください。

抽象基底クラス

 思想的な話です。Pythonの思想として、型に依存せず柔軟性のある記述を許容するものなので、「ある特定の型を継承していないといけない。」という考え方はなじまないようです。なので抽象基底クラスを用意してIFを固定する。といった運用はこれまで取られていなかったという経緯があるようです。(文章からそう解釈しました。)

 ただ機能としては存在するようです。が詳細割愛。C++やJavaとは異なりそうです。

特殊メソッド

 演算子を定義した関数とか、operator+みたいな。pythonでも同じようにこの関数は特殊関数として__XX__の形で用意されています。

 それ以外にも数値に変換する関数(int や float)であったり、ハッシュ値を返すもの、文字列に変換する関数(reprやstr)などが用意されています。

 とても覚えきれないので説明は割愛。リストだけメモっといて困ったらこの章に立ち返るとします。辞書的な使い方をする章ですね。こんなのあったなぁ~って時に引く章です。というかこんなのあったらいいなぁが大体用意されている感じです。

a + b__add__(self, other)
a – b__sub__(self, other)
a * b__mul__(self, other)
a / b__truediv__(self, other)
a // b__floordiv__(self, other)
a % b__mod__(self, other)
pow(a, b, c), a ** b__pow__(self, other [, modulo])
a << b__lshift__(self, other)
a >> b__rshift__(self, other)
a & b__and__(self, other)
a ^ b__xor__(self, other)
a | b__or__(self, other)
二項演算子(演算子の左側オブジェクトの場合) aの関数

 

a + b__radd__(self, other)
a – b__rsub__(self, other)
a * b__rmul__(self, other)
a / b__rtruedeiv__(self, other)
a // b__rfloordiv__(self, other)
a % b__rmod__(self, other)
divmod(a, b)__rdivmod__(self, other)
pow(a, b, c), a ** b__rpow__(self, other)
a << b__rlshift__(self, other)
a >> b__rrshift__(self, other)
a & b__rand__(self, other)
a ^ b__rxor__(self, other)
a | b__ror__(self, other)
二項演算子(演算子の右側のオブジェクトの場合) bの関数

 

-a__neg__(self)
+a__pos__(self)
~a__invert__(self)
abs(a)__abs__(self)
単項演算子

 

a < b__lt__(self, other)
a <= b__le__(self, other)
a == b__eq__(self, other)
a != b__ne__(self, other)
a > b__gt__(self, other)
a >= b__ge__(self, other)
比較演算子

 

a += b__iadd__(self, other)
a -= b__isub__(self, other)
a *= b__imul__(self, other)
a /= b__itruediv__(self, other)
a //= b__ifloordiv__(self, other)
a %= b__imod__(self, other)
a **= b__ipow__(self, other)
a <<= b__ilshift__(self, other)
a >>= b__irshift__(self, other)
a &= b__iand__(self, other)
a ^= b__ixor__(self, other)
a |= b__ior__(self, other)
累算代入文

 

bool(obj)__bool__(self)
complex(obj)__complex__(self)
int(obj)__int__(self)
float(obj)__float__(self)
数値変換

 

round(number[, digit])__round__(self [,digit])
divmod(x, y)__divmod__(self, other)
組み込み関数

 

seq[obj], bin(obj), oct(obj), hex(obj) など__index__(self)
インデックス値

 

map[obj], hash(obj)__hash__(self)
ハッシュ値

 

repr(obj)__repr__(self)
str(obj)__str__(self)
bytes(obj)__bytes__(self)
format(obj, ‘format_spec’)__format__(self, format_spec)
文字列化・バイト列化

 

obj(args, …)__call__(self [,args, …])
関数呼び出し

 

obj.attr__getattr__(self, attr)
obj.attr__getattribute__(self, attr)
obj.attr = value__setattr__(self, attr, value)
del obj.attr__delattr__(self, attr)
dir(obj)__dir__(self)
属性アクセス

 

len(obj)__len__(self)
list(obj)__length_hint(self)
obj[key]__getitem__(self, key)
obj[key] = value__setitem__(self, key, value)
del obj[key]__delitem__(self, key)
key in obj__contains__(self, item)
コンテナインターフェース

tips

 ガベージコレクションとか、文字モード(Unicodeだよ)とか、そんな章です。基本「ふ~ん」だったのですが、1点だけ

イントロスペクション

 introspection : 内省、内観、自己反省 だそうです。このオブジェクトが何者なのか調査する事を指すようです。

 Pythonではこの調査のための関数が色々用意されているから便利に使おうね。って章です。

 まずrepr()で文字列を取ってみる。そのうえで型名が必要であれば、type()を使ってみる。

 次にdir()を使って、オブジェクトのメンバ(属性・メソッド)を調べる。続いて__module__メンバを使ってメンバを調べる。

 最後はソースコードを眺めましょう。とのことでした。

おわり

 本を読んで真面目に勉強しました。文法を真面目にお勉強したことがなかったので、色々新しい発見があり、楽しめました。読むだけでなくメモを取ったり実際動かしたりしながらの方が頭に定着しますね。また別のPython本も読んでみよう。

python
スポンサーリンク
キャンプ工学

コメント

タイトルとURLをコピーしました