デコレータ
デコレータはPythonにおける重要な概念で、元の関数を変更することなく、関数に追加機能を実装するために広く使用されています。
基本的なデコレータ
例えば、李さんはガールフレンドの誕生日プレゼントとしてiPhone12を購入し、箱は未開封の状態でした。
def present():
print('iPhone12')
present() # 実行するとプレゼント情報が表示されます
しかし、プレゼントが物足りないと感じたので、さらに一箱のドブチョコレートとディオールのリップスティックを購入し、美しいギフトボックスに詰め合わせました。ボックスの中にはたくさんの発泡ビーズが詰まっています。
def present():
print('iPhone12')
def box(present_func):
print('='*5 + 'ギフトボックス' + '='*5)
print('一箱の発泡ビーズ')
print('たくさんのチョコレート')
print('一本のディオールリップスティック')
return present_func
present = box(present) # プレゼントを包装してプレゼントとする
present() # プレゼント情報を表示
実行結果は以下のようになります。
=====ギフトボックス=====
一箱の発泡ビーズ
たくさんのチョコレート
一本のディオールリップスティック
iPhone12
このboxがデコレータです。そのパラメータは関数オブジェクトで、数字、文字列、リスト、辞書などのデータ型と同様に、関数とクラスも関数のパラメータとして使用できます。Pythonではすべてが平等であり、すべてがオブジェクトだからです。 boxを使用する際には、依然として元のpresentを返しますが、このpresentを取得する前に2つの追加のサプライズを加えています。そして、boxをpresentとして使用するだけです。
デコレータの本質は、関数をパラメータとして受け取り、関数に対して何らかの処理を行い、元の関数を置き換える高階関数です。 上記の例では、デコレータを使用すると以下のようになります。
def box(present_func): # 関数をパラメータとするデコレータ
print('='*5 + 'ギフトボックス' + '='*5)
print('一箱の発泡ビーズ')
print('たくさんのチョコレート')
print('一本のディオールリップスティック')
return present_func
@box # デコレータを適用、元の関数を自動的に置き換える
def present():
print('iPhone12')
present() # ここでのpresentはデコレートされたpresent、つまり`box(present)`です
実行結果は上記と同じです。
関数のパラメータ処理
李さんは突然、どの色を選ぶかはガールフレンドの意見を聞くべきだと考えました。つまり、元のpresentは選択可能な色のパラメータをサポートすべきです。
def present(color):
print(f'iPhone12{color}版')
几帳面なボーイフレンドとして、李さんは対応する携帯電話の色に合わせて同じ色の発泡ビーズを選ぶ必要があります。つまり、デコレートされたpresent関数のパラメータを取得する必要があります。 この場合、ボックス内部(boxデコレータ)で新しいプレゼントを準備し、色パラメータに基づいて異なる処理を行い、色に基づいて指定されたiPhone12プレゼントを取得する必要があります。
def box(present_func):
print('='*5 + 'ギフトボックス' + '='*5)
def new_present(color): # 新しいプレゼントを準備、元のpresentのパラメータと一致
print(f'{color}色の発泡ビーズ一箱') # 色に基づいて発泡ビーズを準備
print('たくさんのチョコレート')
print('一本のディオールリップスティック')
return present_func(color) # 色に基づいて指定されたiPhone12を取得
return new_present # 新しいプレゼントを返す、新しいプレゼントを呼び出すと追加のサプライズがあり、元のプレゼントpresent_func(color)の結果を返す
@box
def present(color):
print(f'iPhone12{color}版')
present('赤色') # ここでのpresentはboxでデコレートされた狸猫換太子のnew_present関数であり、new_present('赤色')は元のpresent('赤色')の結果を返します
box内部でパラメータに基づいて対応する処理を行うために、新しい関数を構築しました。関数内部にも内部関数を定義できます。内部関数new_presentは外部box関数のパラメータであるpresent_funcを取得して使用できます。 元の関数present_funcのパラメータを取得するために、傀儡関数new_presentを構築する必要があります。この関数は元の関数presentとパラメータと結果が一致し、つまりnew_present('赤色')はpresent_func('赤色')を返します。 そして狸猫換太子して、元のpresent_func関数オブジェクトではなく、置き換えられたnew_present関数オブジェクトを返します。
実行結果は以下のようになります。
=====ギフトボックス=====
赤色の発泡ビーズ一箱
たくさんのチョコレート
一本のディオールリップスティック
iPhone12赤色版
注意:デコレータbox内では、関数オブジェクトを返す必要があります。上記の例では
return present_funcやこの例ではreturn new_presentです。傀儡関数new_presentでは、元の関数present_funcの結果と一致させるために、元の関数の呼び出し結果、つまりpresent_func(color)を返す必要があります。
一般的に言えば、商家として、デコレータboxがどんな形式のプレゼントでも包装できるようにするため、どんなパラメータを持つプレゼントでも対応できるようにする必要があります。これには、傀儡関数new_presentが任意のタイプのパラメータ、つまりdef new_present(*args, **kwargs)をサポートする必要があります。
そして、どんなパラメータ*args, **kwargsも元の関数present_func(*args, **kwargs)に渡すだけです。
変更後、どんなプレゼントでも包装できる汎用的なデコレータが得られます。
def box(present_func):
print('='*5 + 'ギフトボックス' + '='*5)
def new_present(*args, **kwargs): # 任意の数のパラメータを受け取る
if args and len(args) > 0: # パラメータが不確定なため、万一パラメータがある場合、最初のパラメータが色パラメータだと仮定
color = args[0]
print(f'{color}色の発泡ビーズ一箱')
else:
print('発泡ビーズ一箱')
print('たくさんのチョコレート')
print('一本のディオールリップスティック')
result = present_func(*args, **kwargs) # 元の関数の結果を処理したい場合、まず結果を取得
# print(f'元の関数の結果{result}') 元の関数present_funcにreturnがないため、これはNoneです
return result # 元の関数の結果を返す
return new_present
@box
def present(color, pro=False): # 新しいプレゼント関数、2つのパラメータ、デフォルトで12を購入、万一ガールフレンドがProを望む場合にも対応
if pro is True:
print(f'iPhone12 Pro{color}版')
else:
print(f'iPhone12{color}版')
present('海藍色', pro=True)
これにより、デコレートされる関数がいくつかのパラメータを持っていても、boxデコレータは正常に処理できます。 実行結果は以下のようになります。
=====ギフトボックス=====
海藍色の発泡ビーズ一箱
たくさんのチョコレート
一本のディオールリップスティック
iPhone12 Pro海藍色版
パラメータ付きデコレータ
自信満々の李さんは、ボックス上でも何かできると考え、ガールフレンドの好みに応じて異なる形の箱を選択する必要があるため、パラメータに基づいてデコレータboxをカスタマイズする必要があります。ボックスの外側にカスタム関数を追加します。
def custom_box(shape): # パラメータに基づいてデコレータをカスタマイズ
def box(present_func): # デコレータ関数
print('='*5 + f'{shape}ギフトボックス' + '='*5) # 形状に基づいてカスタマイズ
# ...
return box # デコレータ関数を返す
ここで、パラメータに基づいてカスタマイズできるデコレータ関数custom_boxが得られます。このデコレータはパラメータを受け取ると、実際のデコレータboxに渡し、カスタマイズされたboxデコレータ関数を返します。 完全なコードは以下の通りです。
def custom_box(shape): # パラメータに基づいてデコレータをカスタマイズ =====================
def box(present_func): # 実際のデコレータ関数 ---------------------------
print('='*5 + f'{shape}ギフトボックス' + '='*5)
def new_present(*args, **kwargs): # 傀儡関数 ..............
if args and len(args) > 0:
color = args[0]
print(f'{color}色の発泡ビーズ一箱')
else:
print('発泡ビーズ一箱')
print('たくさんのチョコレート')
print('一本のディオールリップスティック')
result = present_func(*args, **kwargs)
return result # 元の関数の結果を返す ......................
return new_present # 傀儡関数を返す ---------------------------
return box # カスタマイズされたデコレータを返す ===============================
@custom_box('ハート形') # カスタマイズ可能なデコレータを使用
def present(color, pro=False):
if pro is True:
print(f'iPhone12 Pro{color}版')
else:
print(f'iPhone12{color}版')
present('海藍色', pro=True)
注意:デコレータはモジュールをインポートする際に即座に計算され、つまり
present('海藍色', pro=True)を呼び出す前に、カスタマイズされたboxが生成されています。
実行後、結果は以下のようになります。
=====ハート形ギフトボックス=====
海藍色の発泡ビーズ一箱
たくさんのチョコレート
一本のディオールリップスティック
iPhone12 Pro海藍色版
ジェネレータとイテレータ
イテラブルオブジェクト
__iter__メソッドを実装し、__iter__メソッドはイテレータを返します
イテレータ
標準的なイテレータプロトコルに従って__iter__と__next__メソッドを実装し、StopIterationで終了
class A:
start = 0
def __iter__(self):
return self
def __next__(self):
if self.start > 10:
raise StopIteration
self.start += 1
return self.start
ジェネレータ
内部でイテレータを実装する関数の一種で、yieldを使用して現在の位置を記録し、1回のイテレーション結果を返します
def fib(max):
n, a, b = 0, 0, 1
while n < max:
print(b)
a, b = b, a + b
n = n + 1
return 'done'
リスト内包表記
内包表記:一括のイテラブルデータ(リストや辞書など)を抽出または処理し、最終的に新しいリストや辞書を得たい場合、内包表記は非常に簡潔な表現方式です。
例えば、一括のデータがあるとします
data = [
{'name': '張三', 'gender': 'male', 'age': 12},
{'name': '李四', 'gender': 'female', 'age': 10},
{'name': '王五', 'gender': 'male', 'age': 20},
{'name': '趙六', 'gender': 'male', 'age': 11},
{'name': '周七', 'gender': 'female', 'age': 16},
{'name': '孫八', 'gender': 'male', 'age': 13},
]
データ内のnameをすべて抽出して新しいリストを作成したい場合、一般的な操作は以下のようです。
names = [] # 空のリストを定義
for item in data: # データを走査
name = item['name'] # 各行からnameを抽出
names.append(name) # リストに追加
内包表記を使用する場合、形式は以下のようです。
names = [item['name'] for item in data] # dataを走査し、各項目のnameを抽出して新しいリストを生成
データ処理
データを抽出する際、各項目データに対して処理を行うこともできます。例えば、各名称の前に'氏名: 'という文字列を追加する必要がある場合、以下のようにします。
names = ['氏名: '+item['name'] for item in data]
'氏名: '+item['name']が各項目のデータです
データフィルタリング
同様にデータをフィルタリングすることもできます。例えば、12歳以上のデータが必要な場合、ifを使用してフィルタリングできます
names = [item['name'] for item in data if item['age']>12]
多重ループ
内包表記は多重ループもサポートしています。例えば
for x in range(1,5)
if x > 2
for y in range(1,4)
if y < 3
x*y
内包表記で表現すると以下のようになります
[x*y for x in range(1,5) if x > 2 for y in range(1,4) if y < 3]
一括操作の実行
内包表記はループ操作の一種であるため、内包表記を使用して類似操作を一括実行することもできます。例えば:
def step1(driver):
print('ステップ1')
def step2(driver):
print('ステップ2')
def step3(driver):
print('ステップ3')
関数名をリストに配置し、内包表記を使用してループ実行できます
steps = [step1, step2, step3] # 関数名リスト
[step(driver) for step in steps] # 変数で受け取る必要はなく、単にループ実行するだけ
辞書内包表記
一括のデータを走査して最終的に辞書を得る必要がある場合、同様に辞書内包表記を使用できます。例えば:
data = [
{'name': '張三', 'gender': 'male', 'age': 12},
{'name': '李四', 'gender': 'female', 'age': 10},
{'name': '王五', 'gender': 'male', 'age': 20},
{'name': '趙六', 'gender': 'male', 'age': 11},
{'name': '周七', 'gender': 'female', 'age': 16},
{'name': '孫八', 'gender': 'male', 'age': 13},
]
{'張三': 12, '李四': 10, ....}のような辞書を得たい場合、辞書内包表記の方法は以下の通りです:
persons = {item['name']: item['age'] for item in data}
辞書内包表記もifフィルタリングなどの操作をサポートしています。
ジェネレータ
ジェネレータは実際には初期データと導出則を含むオブジェクトの一種です。例えば、1万以内のすべての奇数を簡単に書くことができます。なぜなら、1から始めて毎回2を加えるだけで済むからです。 ジェネレータはこのように機能します。大量のデータやCSV/Excelファイルのデータに対して、ジェネレータはメモリを大幅に節約できます。例えば、csv.Reader(f)はジェネレータであり、現在の位置と次のデータを読み取るメソッドのみを保持しています。 走査する必要がある場合、それは毎回1行のデータを読み取って提供します。リスト内包表記の例のように、
data = [
{'name': '張三', 'gender': 'male', 'age': 12},
{'name': '李四', 'gender': 'female', 'age': 10},
{'name': '王五', 'gender': 'male', 'age': 20},
{'name': '趙六', 'gender': 'male', 'age': 11},
{'name': '周七', 'gender': 'female', 'age': 16},
{'name': '孫八', 'gender': 'male', 'age': 13},
]
names = [item['name'] for item in data]
リストの中括弧を小括弧に変更するとジェネレータが得られます
names2 = (item['name'] for item in data)
注意:ジェネレータと内包表記は異なり、そのループは即座に実行されるのではなく、このジェネレータを走査する場合のみ実行されます
for name in names: # リスト内包表記で生成された新しいリストを走査
print(name)
for name in names2: # ジェネレータを走査
print(name)
2つの印刷結果は同じですが、ジェネレータはメモリをより節約し、走査時にのみ実行されます。
特殊メソッド
特殊メソッドはPythonクラス内で特定の特別な役割を持つメソッドで、これらのメソッド名は一般的に__特殊メソッド__、つまり前後に2つのアンダースコアが付いています。
主な特殊メソッド
| 特殊メソッド | 説明 |
|---|---|
\_\_new\_\_ |
クラスを作成し、このクラスのインスタンスを返す |
\_\_init\_\_ |
「コンストラクタ」として理解でき、オブジェクトの初期化時に呼び出され、渡されたパラメータを使用してそのインスタンスを初期化する |
\_\_del\_\_ |
「デストラクタ」として理解でき、オブジェクトがガベージコレクションされる際に呼び出される |
\_\_copy\_\_ |
クラスのインスタンスに対してcopy.copy()を呼び出してオブジェクトの浅いコピーを取得する際の動作を定義 |
\_\_deepcopy\_\_ |
クラスのインスタンスに対してcopy.deepcopy()を呼び出してオブジェクトの深いコピーを取得する際の動作を定義 |
\_\_str\_\_ |
現在のクラスのインスタンスのテキスト表示内容を定義 |
\_\_repr\_\_ |
現在のクラスのインスタンスのテキスト表現(実際)内容を定義 |
\_\_getattribute\_\_ |
属性にアクセスされた際の動作を定義 |
\_\_getattr\_\_ |
存在しない属性にアクセスしようとした際の動作を定義 |
\_\_setattr\_\_ |
属性に値を代入または変更する際の動作を定義 |
\_\_delattr\_\_ |
属性が削除される際の動作を定義 |
\_\_len\_\_ |
カスタムコンテナタイプ用、コンテナの長さを表す |
\_\_getitem\_\_ |
カスタムコンテナタイプ用、self\[key\]が実行された際の動作を定義 |
\_\_setitem\_\_ |
カスタムコンテナタイプ用、self\[key\]=valueが実行された際の動作を定義 |
\_\_delitem\_\_ |
カスタムコンテナタイプ用、項目が削除された際の動作を定義 |
\_\_iter\_\_ |
カスタムコンテナタイプ用、コンテナイテレータ |
\_\_reversed\_\_ |
カスタムコンテナタイプ用、reversed( )が呼び出された際の動作を定義 |
\_\_contains\_\_ |
カスタムコンテナタイプ用、inおよびnot inでメンバーシップをテストする際の動作を定義 |
\_\_missing\_\_ |
カスタムコンテナタイプ用、コンテナ内でkeyが見つからない場合にトリガーされる動作を定義 |
\_\_eq\_\_ |
等号「==」の動作を定義 |
\_\_ne\_\_ |
不等号「!=」の動作を定義 |
\_\_lt\_\_ |
小於符号「<」の動作を定義 |
\_\_gt\_\_ |
大於符号「>」の動作を定義 |
\_\_le\_\_ |
小於等於符号「<=」の動作を定義 |
\_\_ge\_\_ |
大於等於符号「>=」の動作を定義 |
\_\_add\_\_ |
演算子「+」で表される加算を実装 |
\_\_sub\_\_ |
演算子「-」で表される減算を実装 |
\_\_mul\_\_ |
演算子「*」で表される乗算を実装 |
\_\_div\_\_ |
演算子「/」で表される除算を実装 |
\_\_mod\_\_ |
演算子「%」で表される剰余(余り)を求める操作を実装 |
\_\_pow\_\_ |
演算子「**」で表される累乗操作を実装 |
\_\_and\_\_ |
ビット単位のAND操作を実装 |
\_\_or\_\_ |
ビット単位のOR操作を実装 |
\_\_xor\_\_ |
ビット単位のXOR操作を実装 |
オブジェクトの作成と破棄
__new__:オブジェクトの作成を制御するために使用__init__:オブジェクト作成後の初期化操作を制御するために使用__del__:オブジェクト破棄時の操作を制御するために使用
例えば、「シングルトンパターン」は一般的なパターンの一つで、シングルトンパターンは特定のクラスが1つのオブジェクトしか作成できないことを要求します。つまり、毎回作成されるのは同じオブジェクトです。__new__メソッドを使用してオブジェクト(インスタンス)の作成を制御できます。
class Sun:
__obj = None # この属性はクラスの唯一のオブジェクトを格納するために使用
@classmethod
def __new__(cls, *args, **kwargs): # __new__はクラスメソッド
if cls.__obj is None: # もしオブジェクトがまだ作成されていない場合
cls.__obj = super().__new__(cls) # オブジェクトを作成
return cls.__obj # もし__objがすでにオブジェクトを作成している場合、新しいオブジェクトを作成せずにそのオブジェクトを直接返す
def __init__(self):
print('オブジェクトの初期化')
self.name = '太陽' # 初期化操作、例えばオブジェクトに属性を追加
def __del__(self):
print('ああ、私は破壊されようとしています')
sun1 = Sun() # 最初のオブジェクトを作成
sun2 = Sun() # もう一つのオブジェクトを作成しようと試みる
print('2つは同じオブジェクト', sun1 is sun2) # 同じオブジェクト、つまりid(sun1) == id(sun2) メモリアドレスが同じ
実行結果は以下のようになります:
オブジェクトの初期化
オブジェクトの初期化
2つは同じオブジェクト True
ああ、私は破壊されようとしています
注意:
__new__を呼び出した後に自動的に__init__が呼び出されるため、__new__が常に同じオブジェクトを返しても、__init__を使用してオブジェクト属性を変更できます。
オブジェクトのコピー
__copy__ :オブジェクトをコピーする際の操作を制御するために使用
__deepcopy__:オブジェクトを深くコピーする際の操作を制御するために使用
浅いコピーと深いコピーはPythonの知識点の一つです。リストや辞書に他のリストや辞書タイプがネストされている場合、浅いコピーはサブリスト/辞書の参照アドレスのみをコピーし、深いコピーはサブリスト/辞書内のすべての変数をコピーします。 例は以下の通りです:
from copy import copy, deepcopy
l1 = [1, 'b', ['c', 'd', 'e']]
l2 = copy(l1) # 浅いコピー、リスト内にネストされた可変タイプは参照アドレスのみをコピー
l3 = deepcopy(l1) # 深いコピー、リスト内にネストされた可変タイプ内のすべての変数をコピー
l1[2][0] = 'c1' # l1内のネストされたリストの最初の値を変更
print(l1)
print(l2) # l2も変更に追随
print(l3) # l3は影響を受けない
特殊メソッド__copy__および__deepcopy__を使用して、copy()およびdeepcopy()操作時のオブジェクトの動作を制御できます。
例えば:
from copy import copy, deepcopy
class Planet:
"""惑星"""
class Mercury(Planet):
"""水星"""
class Venus(Planet):
"""金星"""
class Earth(Planet):
"""地球"""
# ... 他の惑星は省略
class Sun:
def __init__(self):
self.name = '太陽'
self.planets = []
def add_planet(self, planet):
self.planets.append(planet)
def __copy__(self):
new_sun = Sun()
new_sun.name = self.name
new_sun.planets = self.planets # コピー後の新しい太陽と元の太陽はすべての惑星を共有
return new_sun
def __deepcopy__(self, memodict={}):
new_sun = Sun()
new_sun.name = self.name
for planet in self.planets: # 新しい太陽をコピーし、元の太陽のすべての惑星をコピーして、独立した惑星を持つ
new_sun.add_planet(planet.__class__()) # 惑星のクラスを使用して新しいオブジェクトを作成
return new_sun
sun = Sun()
sun.add_planet(Mercury())
sun.add_planet(Venus())
sun.add_planet(Earth())
new_sun1 = copy(sun)
print(new_sun1.planets[0] is sun.planets[0]) # 同じ水星
new_sun2 = deepcopy(sun)
print(new_sun2.planets[0] is not sun.planets[0]) # 同じ水星ではない
表示結果は以下のようになります:
True
True
オブジェクトおよび型情報
Python3では、str()とrepr()は両方ともオブジェクトを文字列形式に変換しますが、違いがあります:
- str():テキスト表示で、人間が見やすい形式
- repr(): オブジェクト表現、型と実際の形式をより明確に表示 例えば:
a = '''
hello
world
'''
print(a) # もしaが文字列タイプでない場合、print(a)はprint(str(a))と同等
print(repr(a))
表示結果は以下のようになります:
hello
world
'\nhello\nworld\n'
repr()を使用すると、aが文字列(前後に一重引用符)であり、3つの改行を含むことがより明確にわかります。
strおよびreprのデフォルト使用シナリオは以下の通りです:
- str(obj)メソッドを呼び出す場合:オブジェクトの
__str__メソッドを呼び出します(もし親クラスのobjectの__str__メソッドを呼び出さない場合) - repr(obj)メソッドを呼び出す場合:オブジェクトの
__repr__メソッドを呼び出します(もし親クラスのobjectの__repr__メソッドを呼び出さない場合) - print(obj)時、オブジェクトに
__str__メソッドがある場合はその__str__メソッドを呼び出し、そうでない場合はその__repr__メソッドを呼び出そうとし、なければ順次親クラスの__str__または__repr__メソッドを呼び出します - Python対話コマンドラインで直接
>>> objと入力してEnterを押すと、オブジェクトの__repr__メソッドを呼び出します(もし親クラスのobjectの__repr__メソッドを呼び出さない場合)
クラスを定義する際、__str__および__repr__メソッドを使用して、印刷またはデバッグ時にオブジェクトをより明確にできます。
class Sun:
"""太陽"""
def __init__(self):
self.name = '太陽'
def __str__(self): # オブジェクトを文字列形式に変換する値を定義
return self.name
def __repr__(self): # オブジェクトの実際の表現形式を定義
return "<Sun %s>" % self.name
sun = Sun()
print('オブジェクトのテキスト形式', sun) # print(sun)はprint(str(sun))と同等 str(obj)はobj.__str__()を呼び出すと同等
print('オブジェクトの表現形式', repr(sun)) # # repr(obj)はobj.__repr__()を呼び出すと同等
実行結果は以下のようになります:
オブジェクトのテキスト形式 太陽
オブジェクトの表現形式 <Sun 太陽>
オブジェクトの他の属性やクラス関連属性を確認したい場合は、以下を参照してください:
# オブジェクトのすべての属性およびメソッドを取得
print(dir(sun))
# クラス関連属性を確認
print('オブジェクトの所属クラス', sun.__class__,
'クラス名', sun.__class__.__name__,
'クラスコメント', sun.__class__.__doc__,
'クラスの親クラス', sun.__class__.__base__,
'クラスのすべての親クラス', sun.__class__.__bases__,
)
実行結果は以下のようになります:
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'name']
オブジェクトの所属クラス <class '__main__.Sun'> クラス名 Sun クラスコメント 太陽 クラスの親クラス <class 'object'> クラスのすべての親クラス (<class 'object'>,)