Contents
Pythonのクラスで反復処理1を行う方法の解説記事です。
各コードはGitHub repositoryからダウンロードできます。
iteration = IterClass(["a", "ab", "abc", "bb", "cc"])
print([v for v in iteration if "a" in v])
# ['a', 'ab', 'abc']
実行環境 | |
---|---|
OS | Windows Subsystem for Linux |
Python | version 3.8.5 |
解決方法
クラスの特殊メソッド__iter__
2とyield from
構文3によって実装できます。リストまたはタプルを記憶し、繰り返し処理として呼び出された際に先頭から順番に返します。
class IterClass(object):
"""
Iterable class.
Args:
values (list[object] or tuple(object)): list of values
Raises:
TypeError: @values is not a list/tuple
"""
def __init__(self, values):
if not isinstance(values, (list, tuple)):
raise TypeError("@values must be a list or tuple.")
self._values = values
def __iter__(self):
yield from self._values
応用(__bool__
との組み合わせ)
上記の例では新しいクラスを作成する意義が感じられないので、応用バージョンを作りました。
最小単位の作成
反復処理で返される最小単位Unit
クラスをまず作成します。
class Unit(object):
"""
The smallest unit.
"""
def __init__(self, value):
if not isinstance(value, (float, int)):
raise TypeError(
f"@value must be integer or float value, but {value} was applied.")
self._value = value
self._enabled = True
def __bool__(self):
return self._enabled
@property
def value(self):
"""
float: value of the unit
"""
return self._value
def enable(self):
"""
Enable the unit.
"""
self._enabled = True
def disable(self):
"""
Disable the unit.
"""
self._enabled = False
Unit
は固有の値と状態(__bool__
: True/False)を持っています。
Unitの作成:
unit1 = Unit(value=1.0)
# Unitの状態がTrueであれば値を表示
if unit1:
print(unit1.value)
# 1.0
状態をFalseに:
# Disable
unit1.disable()
# Unitの状態がTrueであれば値を表示
if unit1:
print(unit1.value)
# 出力なし
状態をTrueに:
# Disable
unit1.enable()
# Unitの状態がTrueであれば値を表示
if unit1:
print(unit1.value)
# 1.0
反復処理を行うクラス
複数のUnit
の情報を保管して反復処理を行うクラスSeries
を作成します。
class Series(object):
"""
A series of units.
"""
def __init__(self):
self._units = []
def __iter__(self):
yield from self._units
def add(self, unit):
"""
Append a unit.
Args:
unit (Unit]): the smallest unit
Raises:
TypeError: unit is not an instance of Unit
"""
if not isinstance(unit, Unit):
raise TypeError("@unit must be a instance of Unit")
self._units.append(unit)
def _validate_index(self, num):
"""
Validate the index number.
Args:
num (int): index number of a unit
Raises:
TypeError: @num is not an integer
IndexError: @num is not a valid index number
"""
if not isinstance(num, int):
raise TypeError(
f"@num must be integer, but {num} was applied.")
try:
self._units[num]
except IndexError:
raise IndexError(f"@num must be under {len(self._units)}")
def enable(self, num):
"""
Enable a unit.
Args:
num (int): index of the unit to be enabled
Raises:
TypeError: @num is not an integer
IndexError: @num is not a valid index number
"""
self._validate_index(num)
self._units[num].enable()
def disable(self, num):
"""
Disable a unit.
Args:
num (int): index of the unit to be disabled
Raises:
TypeError: @num is not an integer
IndexError: @num is not a valid index number
"""
self._validate_index(num)
self._units[num].disable()
状態がTrueとなっているUnitの値を返す関数
Series
クラスの挙動を確認するため、状態がTrueとなっているUnitの値を返す関数を作ります。
def show_enabled(series):
"""
Show the values of enabled units.
"""
if not isinstance(series, Series):
raise TypeError("@unit must be a instance of Series")
print([unit.value for unit in series if unit])
挙動確認
Unit
をSeries
に登録し、すべてのUnit
の値を表示します。
# Create a series of units
series = Series()
[series.add(Unit(i)) for i in range(6)]
show_enabled(series)
# [0, 1, 2, 3, 4, 5]
5, 6番目のUnit(値は4, 5)の状態をFalseにすると...
# Disable two units
series.disable(4)
series.disable(5)
show_enabled(series)
# [0, 1, 2, 3]
5番目のUnit(値は4)の状態をTrueに戻すと...
# Enable one disabled unit
series.enable(4)
show_enabled(series)
# [0, 1, 2, 3, 4]
あとがき
閲覧ありがとうございます。お疲れ様でした。
この記事を作成したきっかけ:
自作のPython package CovsirPhy: COVID-19 analysis with phase-dependent SIRsでこの仕組みを使いました。