pikesaku’s blog

個人的な勉強メモです。記載内容について一切の責任は持ちません。

Pythonのジェネレータ関数

ジェネレータ関数とは?

 

  • ジェネレータオブジェクトを生成する関数
  • ジェネレータオブジェクトはイテレータ処理(データを順番に処理)をする機能を持つ
  • オブジェクト生成時にデータを全て読み込まない。__next__メソッド実行時に、1つづつ読み込む。大きなデータ、ネットワーク通信処理時に省メモリの効果あり。
  • yield式を含む。yield実行時に、呼び出し元にデータと制御を戻す

 

動作確認

 

  • データ生成
$ seq 10000 | xargs -I {} echo "line {}" > ./data
$ head -3 ./data 
line 1
line 2
line 3
$ tail -3 ./data 
line 9998
line 9999
line 10000
  • サンプルコード
def gen():
  for l in open('./data'):
    l = l.strip()
    yield l

a = gen()

for i in range(3):
  print(a.__next__())
  • 実行結果
line 1
line 2
line 3
  • 考察

__next__メソッドを呼び出す度にforループが1回回る。
__next__メソッド呼び出される迄は、処理待ち状態。
  
 

sendメソッド

 
処理待ち状態のジェネレータオブジェクトにデータを渡す。
 

  • サンプルコード
def gen():
  for l in open('./data'):
    l = l.strip()
    v = yield l
    if v is None:
      print('moge')
    else:
      yield l + ' ' + v

a = gen()

print('1 回目nextメソッド実行 ========================')
print(a.__next__())
print()

print('2 回目nextメソッド実行 ========================')
print(a.__next__())
print()

print('sendメソッド実行 ========================')
print(a.send('huga'))
print()

print('3 回目nextメソッド実行 ========================')
print(a.__next__())
  • 実行結果
1 回目nextメソッド実行 ========================
line 1

2 回目nextメソッド実行 ========================
moge
line 2

sendメソッド実行 ========================
line 2 huga

3 回目nextメソッド実行 ========================
line 3
  • 考察

v = yield lのように、yield結果をオブジェクトに入れる記述が必要。
nextメソッド実行時は、vにNoneが入る。yield実行時にデータと制御を呼び出し元に戻し処理は停止。
・1回目nextメソッド実行時に、moge出力なし
・2回目nextメソッド実行時に、初めてif v is None:が動作。この時のvはNone(1回目nextメソッド実行結果)
sendメソッド実行時は、vに引数に指定したオブジェクトが入り処理が再開。
 

  • 参考

Pythonのイテレータ、ジェネレータまわりの言語仕様について復習してみる - Qiita
 
 

throwメソッド

 
処理待ち状態のジェネレータオブジェクトに例外を渡す。

  • サンプルコード
def gen():
  for l in open('./data'):
    l = l.strip()
    v = yield l
    if v is None:
      print('moge')
    else:
      yield l + ' ' + v

a = (gen())

print('1 回目nextメソッド実行 ========================')
print(a.__next__())
print()

print('2 回目nextメソッド実行 ========================')
print(a.__next__())
print()

print('throwメソッド実行 ========================')
print(a.throw(ValueError('error occur')))
print()

print('3 回目nextメソッド実行 ========================')
print(a.__next__())
  • 実行結果
1 回目nextメソッド実行 ========================
line 1

2 回目nextメソッド実行 ========================
moge
line 2

throwメソッド実行 ========================
Traceback (most recent call last):
  File "./a.py", line 21, in <module>
    print(a.throw(ValueError('error occur')))
  File "./a.py", line 4, in gen
    v = yield l
ValueError: error occur

 
 

closeメソッド

 
処理待ち状態のジェネレータオブジェクトにGeneratorExit例外を渡し、ジェネレータ処理を正常終了させる。

  • サンプルコード
def gen():
  for l in open('./data'):
    l = l.strip()
    v = yield l
    if v is None:
      print('moge')
    else:
      yield l + ' aaa ' + v

a = (gen())

print('1 回目nextメソッド実行 ========================')
print(a.__next__())
print()

print('2 回目nextメソッド実行 ========================')
print(a.__next__())
print()

print('closeメソッド実行 ========================')
print(a.close())
print()

print('3 回目nextメソッド実行 ========================')
print(a.__next__())
  • 実行結果
1 回目nextメソッド実行 ========================
line 1

2 回目nextメソッド実行 ========================
moge
line 2

closeメソッド実行 ========================
None

3 回目nextメソッド実行 ========================
Traceback (most recent call last):
  File "./a.py", line 25, in <module>
    print(a.__next__())
StopIteration
  • 考察

closeメソッド実行時に、forループは停止。
closeメソッド実行後のジェネレータオブジェクトにnextメソッド実行するとStopIteration例外が発生

Pythonのglobalとnonlocal宣言

global宣言

グローバル変数を関数内で参照できるが、データ変更しても、新しいローカル変数が定義された状態になる。(もしくはUnboundLocalError例外)
global宣言を使えば、関数内でグローバル変数のデータ変更が可能
 

サンプルコード

VAR = 1

def hoge(a,b):
  global VAR
  print(VAR,a,b)
  VAR = 4
  print(VAR,a,b)

hoge(2,3)
print(VAR)

実行結果

1 2 3
4 2 3
4

 
 

nonlocal宣言

関数内に関数を定義する場合、内側の関数から外側の関数内の変数を変更するために利用
考え方は、global変数と同じ。

サンプルコード

def hoge_e(a,b):
  c = a
  def hoge_i():
    nonlocal c
    c = b
    return c

  return hoge_i()


h = hoge_e(1,2)
print(h)

実行結果

2

サンプルコードhoge_iのように、外側の値を参照する内側の関数をクロージャという

Pythonの関数(引数の指定方法)

キーワード引数

引数に名前をつけて渡す。順番が関係なくなる。

>>> def hoge(a1, a2, a3):
...   print(a1,a2,a3)
... 
>>> hoge(1,2,3)
1 2 3
>>> hoge(a3=1,a2=2,a1=3)
3 2 1
>>> 


引数をまとめて渡す方法

リストの要素を引数で渡す場合(*を付ける)

>>> def hoge(a1, a2, a3):
...   print(a1,a2,a3)
... 
>>> hoge(1,2,3)
1 2 3
>>> a = [5,6,7]
>>> hoge(*a)
5 6 7
>>> hoge(a)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: hoge() missing 2 required positional arguments: 'a2' and 'a3'
>>> 

hoge(a)で渡すと、リストオブジェクト1つだけが引数扱い。関数側で引数不足になる。
hoge(*a)で渡せば要素が引数となる。


ディクショナリのKey,Valueをキーワード引数で渡す場合(**をつける**)

>>> def hoge(a1, a2, a3):
...   print(a1,a2,a3)
... 
>>> a = {'a1':1, 'a2':2, 'a3':3}
>>> hoge(**a)
1 2 3
>>> hoge(*a)
a2 a3 a1
>>> 

アスタリスク(*)2個の場合はkey,valueがキーワード引数で渡される。
1個の場合は、キーが渡される
 

デフォルト引数

引数の省略が可能。デフォルト値が利用される。

>>> def hoge(a1,a2,a3=3):
...   print(a1,a2,a3)
... 
>>> hoge(1,2)
1 2 3
>>> 

 

可変長引数

引数の数を事前に決めない方法

タプルで受ける場合(*をつける)

>>> def hoge(a1,*b):
...   print(a1,b)
... 
>>> hoge(1,2,3,4,5)
1 (2, 3, 4, 5)
>>> 



ディクショナリで受ける場合(**をつける)

>>> def hoge(a1,**b):
...   print(a1,b)
... 
>>> hoge(1,2,3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: hoge() takes 1 positional argument but 3 were given
>>> hoge(1,a2=2,a3=3)
1 {'a2': 2, 'a3': 3}
>>>



他記述(*だけ)

キーワード引数限定にする

>>> def hoge(a1, *, k1='huga'):
...   print(a1,k1)
... 
>>> hoge(1)
1 huga
>>> hoge(1,2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: hoge() takes 1 positional argument but 2 were given
>>> hoge(1,k1=1)
1 1
>>> hoge(1,k2=1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: hoge() got an unexpected keyword argument 'k2'
>>> 

a1の後に、*を記述すると、以降はキーワード引数以外だと例外発生する指示になる。(上記1つ目の例外)
ただキーワード引数でも、関数側で定義ないキーワードでは受けない。(上記2つ目の例外)

Pythonの内包表記

リスト

>>> a = [ x**2 for x in range(1,11)]
>>> a
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
>>> 

※rangeの2個目の引数は終了点。range(1,11)1は1〜10

セット

>>> a = { x**2 for x in range(1,11)}
>>> a
{64, 1, 4, 36, 100, 9, 16, 49, 81, 25}
>>> 

ディクショナリ

>>> a = {i+1:i**2 for i in range(1,11)}
>>> a
{2: 1, 3: 4, 4: 9, 5: 16, 6: 25, 7: 36, 8: 49, 9: 64, 10: 81, 11: 100}
>>>

ジェネレータ

>>> a = (i**2 for i in range(1,11))
>>> next(a)
1
>>> next(a)
4
>>> 

2重ループ

>>> v = [[1,2],[3,4],[5,6]]
>>> v
[[1, 2], [3, 4], [5, 6]]
>>> [i for a in v for i in a] 
[1, 2, 3, 4, 5, 6]
>>> 

左のループ(for a in v)が親ループ。ループで書くと以下

>>> v = [[1,2],[3,4],[5,6]]
>>> w = list()
>>> for a in v:
...   for i in a:
...     w.append(i)
... 
>>> w
[1, 2, 3, 4, 5, 6]
>>> 

PythonのオブジェクトID

オブジェクトIDとは?

インタプリタがオブジェクトを管理するID
メモリ上のオブジェクトの場所を示す情報

2つのオブジェクトが同じかは、is で判定可能
id関数で確認できる

>>> a = [1,2]
>>> b = [1,2]
>>> id(a)
4391089224
>>> id(b)
4391101704
>>> a == b
True
>>> a is b
False
>>> 

リストは可変。2つのオブジェクトは別ものとして管理。
イミュータブル(不変)なオブジェクトのstringで値が同じ場合は同一のIDとなる。
メモリ効果がある。

>>> a = 'abc'
>>> b = 'abc'
>>> id(a)
4389672752
>>> id(b)
4389672752
>>> a == b
True
>>> a is b
True
>>>

PythonのNone型

値が存在しないことを表す特別な値

>>> a = ['',None,1,2]
>>> a
['', None, 1, 2]
>>> a[0] is None
False
>>> a[1] is None
True
>>> a[0]
''
>>> if a[0]:
...   print('test')
... 
>>> if a[1]:
...   print('test')
... 
>>> 

NoneはFalseと判定される
is NoneでNoneであるか判定する。

Pythonのディクショナリ操作

定義方法

>>> a = {'a':1,'b':2}
>>> a
{'b': 2, 'a': 1}
>>> a['c'] = 3
>>> a
{'b': 2, 'a': 1, 'c': 3}
>>> 

キーの存在チェック

>>> a
{'b': 2, 'a': 1, 'c': 3}
>>> 'a' in a
True
>>> 'd' in a
False
>>>

インデックスアクセス時に存在しない場合にデフォルト値を返す

>>> a
{'b': 2, 'a': 1, 'c': 3}
>>> a['d']
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'd'
>>> a.get('d')
>>> a.get('d','hoge')
'hoge'
>>> 

getメソッドは引数でデフォルト値を設定できる。引数がない場合は、Noneを返す。

イテレーションアクセス

キーにアクセスする場合

>>> a
{'b': 2, 'a': 1, 'c': 3}
>>> for k in a:
...   print(k)
... 
b
a
c
>>> 

値にアクセスする場合

>>> a
{'b': 2, 'a': 1, 'c': 3}
>>> for v in a.values():
...   print(v)
... 
2
1
3
>>> 

両方にアクセスする場合

>>> a
{'b': 2, 'a': 1, 'c': 3}
>>> for k,v in a.items():
...   print(k,v)
... 
b 2
a 1
c 3
>>>

データの追加と削除

追加

>>> a
{'b': 2, 'a': 1, 'c': 3}
>>> a['d'] = 4
>>> a
{'b': 2, 'a': 1, 'd': 4, 'c': 3}
>>> 

定義時と同じ

削除

>>> a
{'b': 2, 'a': 1, 'd': 4, 'c': 3}
>>> del a['a']
>>> a
{'b': 2, 'd': 4, 'c': 3}
>>> a.pop('b')
2
>>> a
{'d': 4, 'c': 3}
>>> 

delとpopの違いは、popの場合は値を返す点
存在しないキーを指定してアクセスするとKeyErrorが発生する。

popを使うとKeyErrorは発生させないことが可能。

>>> a
{'d': 4, 'c': 3}
>>> del a['a']
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'a'
>>> a.pop('a')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'a'
>>> a.pop('a', 'hoge')
'hoge'
>>> a
{'d': 4, 'c': 3}
>>>