pythonメニュー

Pythonの補足

with

機能を使う場合に、所定の順番の手続きが必要な場合、その手続きを補佐して、間違いを少なく簡潔に使えるようにする仕組みです。
例えば、ファイル操作では一般に最初にファイルを開く処理が必要で、最後に閉じなければなりません。
そのような場合、withを使えば閉じる処理を省略できます。
内部的には、with構文で使えるクラスには、2つのメソッドを実装します。
__enter__(self)
__exit__(self, exception_type, exception_value, traceback)

以下で具体例を示します。
matplotlibを生成して、(x,y)を複数セットとするだけで、with終了のタイミングでグラフをプロットさせる。
そのためのPlot2クラスを、plot2.pyに次の内容で作成する場合の骨組みを示します。
import numpy as np
import matplotlib.pyplot as plt
class Plot2:
   def __init__(self):
      print("init")
   #
   def __enter__(self):
      print("enter")
      return self
   #
   def __exit__(self, ex_type, ex_value, trace):
      print("exit: ", ex_type, ex_value, trace)
#

with Plot2() as ploter:
   pass
init
enter
exit:  None None None
以上を元にして、plot2.pyを次のように作ってみました。
import numpy as np
import matplotlib.pyplot as plt
class Plot2:
   def __init__(self):
      self.x = np.array([])
      self.y = np.array([])
   #
   def __enter__(self):
      return self
   #
   def setXY(self,x,y):
      self.x = np.append( self.x, [x] )
      self.y = np.append( self.y, [y] )
   #
   def __exit__(self, ex_type, ex_value, trace):
      plt.grid() # グリッドの表示
      plt.plot(self.x, self.y)
      plt.show() #表示されると、plt内容はクリア
   #
import math
with Plot2() as w:
   for x in range(-50, 50, 1):
      y=1 / (1 + math.exp(-x))
      #print(x, y)
      w.setXY(x, y)

補足

#  次の実行で、ローカル名前空間の変数が、辞書形式で表示される。
def test(a = 'A'):
   v = 10
   print( locals() ) # 表示:{'a': 'A', 'v': 10}

f=test # 関数を記憶
f() # 上記test関数の実行

# 次の実行で、グローバルな名前空間の変数が、辞書形式で表示される。
print( globals() ) # 色々表示するが、上記の'f'や'test'はグローバル関数として確認できる。

# 次で継承関係をチェックます。(第1引数が 第2引数 サブクラスである場合に True)
print( issubclass(int , object) ) # True つまり int も objectのサブクラスと分かる。
print( issubclass(object , int) ) # False

print(クラス名.__bases__)# クラスのスーパークラスをタプルで取得する

import inspect
print( inspect.getmro(クラス名) ) # クラスが継承しているクラス(自身も含め)をタプルで取得する

print( インスタンス.__class__  ) # インスタンスが属しているクラスを取得する

クロージャー(closure:閉鎖)=は関数閉包(へいほう)とも呼ばれる

関数の中に関数を定義することができ、その際に外側の関数で宣言された変数を内側の関数で操作することができる。
これにより、グローバル変数の削減がなどが利点として挙げられる。
(グローバルスコープ以外で定義された関数が, 「定義時」の自分を囲むスコープの情報を記憶している)
def sub2(data):
   data *= 2 # アクセス対象の変数
   def sub1():
       # data += 1 # 変更できないことに注意
       print( data + 1 )  # 対象の変数をアクセス
   #
   return sub1
   # 上記の関数内部で作った関数を返す

f1 = sub2(5); # 戻ってきた関数は、クロージャにより関数自身を囲むスコープの情報(data)を記憶している。
f2 = sub2(9);
f1()  # 「11」を出力
f2()  # 「19」を出力
これが使えると、例えばイベント用など引数が決まっている関数sub1を定義する時、その位置のすぐ上で sub1に渡す情報を変数を用意できるので、可読性が良くなります。

デコレータ decorator

関数を引数に取り、その関数を利用して新しい機能を付け加えた関数を作る機能と言えます。
左が「@記号を用いたシンタックスシュガー」を使わない技法で、右が使った手法です
(前述のクロージャのアクセス対象は変数dataでしたが、それと同じ理屈で引数の関数をアクセス対象にいて実現している)
def sub2(func): # これが「デコレータ」となる関数です。
   def sub1(data):
       print(data , "の引数で実行=" , end="" )
       ret = func(data) # >デコレート対象の関数アクセス
       return ret - 1 # 1つ減らして戻す
   #
   return sub1
   # 上記の関数内部で作った関数を返す

def plus4(data): # 機能を付け加える前となる関数の例
   return  data + 4

plus4 = sub2(plus4)

plus4(5) # 「5 の引数で実行=8」
plus4(9) # 「9 の引数で実行=12」
def sub2(func): # これが「デコレータ」となる関数です。
   def sub1(data):
       print(data , "の引数で実行=" , end="" )
       ret = func(data) # >デコレート対象の関数アクセス
       return ret - 1 # 1つ減らして戻す
   #
   return sub1
   # 上記の関数内部で作った関数を返す

@sub2
def plus4(data): # 機能を付け加える前となる関数の例
   return  data + 4


plus4(5) # 「5 の引数で実行=8」
plus4(9) # 「9 の引数で実行=12」
上記右の例で、別途に作るmult3関数に、sub2関数のデコーダを施す例を以下に示します。
@sub2
def mult3(data): # 機能を付け加える前となる関数の例
   return  data * 3

mult3(2) # 「2 の引数で実行=5」

ジェネレータ generator

ジェネレータは、プログラムにおいて、順次に要素の値などを次々と生成(ジェネレート)し他の手続きに渡す機能の関数です。
具体的には、繰り返しの中で、return の代わりにyield(イールド:産出する)を使って複数を生成しながら返すように作られます。
return を使うと、そこで[情報]を返して終わりますが、yieldでは[情報]を返す流れを生成したあと、終わらずに次の処理が進められます。
次の右側が実行例で、それから意味を考えるとよいでしょう。
def gen():
  for v in range(20,120, 20):
     yield v;
     print(v)

g=gen()
for v in g:
  print(v-5)

print(g)
15
20
35
40
55
60
75
80
95
100
<generator object gen at 0x00000190BCFE80A0>




from __future__ import annotations

pythonの型ヒントで自作クラスを指定する場合に指定します。
Python3.7未満の表現(自己クラスは文字列で指定) Python3.8以上 実行による出力結果


class SNode: # 片方向リストノード
  def __init__(self, node: 'SNode', data: int): #  
    if node : node.next=self
    self.data=data
    self.next=None
  def list_print(self): # 再帰関数
    print(self.data)
    if self.next: self.next.list_print() # 再帰呼び出し

top=SNode(None, 100) # 先頭ノード生成
tmp=SNode(top, 200)
tmp=SNode(tmp, 300)
tmp=SNode(tmp, 456)

top.list_print() # リストを辿って、データを表示
from __future__ import annotations # Python3.7以上で使う

class SNode: # 片方向リストノード
  def __init__(self, node: SNode, data: int):
    if node : node.next=self
    self.data=data
    self.next=None
  def list_print(self): # 再帰関数
    print(self.data)
    if self.next: self.next.list_print() # 再帰呼び出し

top=SNode(None, 100) # 先頭ノード生成
tmp=SNode(top, 200)
tmp=SNode(tmp, 300)
tmp=SNode(tmp, 456)

top.list_print() # リストを辿って、データを表示
100
200
300
456
'SNode''Snode'と書いてもエラーにならない 'SNode'SNodeと文字列でない表現が可能になります。