【Python】MutableなSequenceを定義してみた
Pythonの勉強のため、deapにおけるIndividualを例に取ってMutableSequenceな自作クラスを定義してみた。
Sponsored by Google AdSense
import sys
import deap
print(sys.version)
3.9.13 | packaged by conda-forge | (main, May 27 2022, 17:01:00)
[Clang 13.0.1 ]
print(f"deap: {deap.__version__}")
deap: 1.3
deapにおける ‘Individual’
# https://deap.readthedocs.io/en/master/overview.html#types
from deap import base, creator
# FitnessMinの定義
creator.create("FitnessMin", base.Fitness, weights=(-1.0,))
# FitnessMinを使ってIndividualの定義
creator.create("Individual", list, fitness=creator.FitnessMin)
たとえば以下のような使い方が想定されている。
# リストのような振る舞いをする
individual = creator.Individual(range(3))
print(individual)
[0, 1, 2]
# 適応度を代入すると
individual.fitness.values = (1.23,)
# 重みをかけた値を取得できる
print(individual.fitness.wvalues)
(-1.23,)
要するに
- リストのように遺伝子を配列で保持できて、
- fitness (適合度) に関する情報をattributeとして持つことができて、さらに
- fitnessに重み付け (1もしくは-1をかけることで最小化問題に統一) することができる
ようなオブジェクトを生成したいだけ。
deapにより定義されたIndividual
を代替するものをきちんと定義したいという思いをきっかけに、
上記の3条件を満たすオブジェクトを私の趣味を存分に取り入れながら定義してみようと思う。
(無駄に凝って型付けしがち)
自作の ‘Individual’
実装
from typing import (
Literal,
Optional,
Union,
TypeVar,
overload,
SupportsIndex,
)
from collections.abc import MutableSequence, Iterable, Iterator
from numbers import Number
import random
T = TypeVar("T")
class Individual(MutableSequence[T]):
def __init__(
self,
__genes: Iterable[T],
*,
sign: Literal[1, -1] = 1,
seed: Optional[int] = None,
fitness: Optional[Number] = None,
) -> None:
"""遺伝的アルゴリズムにおける個体を表すクラス。
Parameters
----------
__genes : Iterable[T]
遺伝子。個体の表現。
sign : Literal[1, -1], optional
符号。1または-1。
最小化問題の場合は1、最大化問題の場合は-1。, by default 1
seed : Optional[int], optional
シード値。Noneの場合はランダムに割り当てられる, by default None
fitness : Optional[Number], optional
適応度。基本的にインスタンス生成時には代入しない, by default None
"""
self.__genes = list(__genes)
self.__fitness = fitness
if seed is None:
self.__seed = random.randint(0, 2**31 - 1) # int32の範囲
else:
self.__seed = seed
if sign not in {1, -1}:
raise ValueError("sign must be 1 or -1")
else:
self.__sign = sign
@property
def seed(self) -> int:
return self.__seed
@property
def sign(self) -> Literal[1, -1]:
"""符号。1または-1。
最小化問題の場合は1、最大化問題の場合は-1。
"""
return self.__sign
@property
def fitness(self) -> Optional[Number]:
"""適応度。Noneの場合は未評価。"""
return self.__fitness
@seed.setter
def seed(self, __value) -> None:
raise AttributeError("can't set attribute")
@sign.setter
def sign(self, __value) -> None:
raise AttributeError("can't set attribute")
@fitness.setter
def fitness(self, __value: Number) -> None:
self.__fitness = __value
@overload
def __setitem__(self, __index: SupportsIndex, __value: T) -> None:
...
@overload
def __setitem__(self, __index: slice, __value: Iterable[T]) -> None:
...
# MutableSequenceのabstractmethod
def __setitem__(
self,
__index: Union[SupportsIndex, slice],
__value: Union[T, Iterable[T]],
) -> None:
self.__genes.__setitem__(__index, __value)
@overload
def __getitem__(self, __index: SupportsIndex) -> T:
...
@overload
def __getitem__(self, __index: slice) -> "Individual[T]":
...
# MutableSequenceのabstractmethod
def __getitem__(
self, __index: Union[SupportsIndex, slice]
) -> Union[T, "Individual[T]"]:
# スライスの場合、Individualを返す
if isinstance(__index, slice):
return self.__class__(
self.__genes.__getitem__(__index),
sign=self.__sign,
seed=self.__seed,
fitness=self.__fitness,
)
else:
return self.__genes.__getitem__(__index)
# MutableSequenceのabstractmethod
def __delitem__(self, __index: Union[SupportsIndex, slice]) -> None:
self.__genes.__delitem__(__index)
# MutableSequenceのabstractmethod
def __len__(self) -> int:
return self.__genes.__len__()
# MutableSequenceのabstractmethod
def insert(self, __index: SupportsIndex, __value: T) -> None:
return self.__genes.insert(__index, __value)
def __str__(self) -> str:
# オブジェクトの中身がよくわかるように定義
return "{}({}, sign={}, seed={}, fitness={})".format(
self.__class__.__name__,
self.__genes,
self.__sign,
self.__seed,
self.__fitness,
)
def __repr__(self) -> str:
# print()で表示される内容を定義
return self.__str__()
def __add__(self, other: Iterable[T]) -> "Individual[T]":
# Individual同士の結合を定義
# このとき、signやseed、fitnessは左側のIndividualのものを引き継ぐ
self.__genes += list(other)
return self
標準ライブラリのみで実装されている。なお趣味でたくさん型付けをしているため、Python3.9以上でないと正しく動作しない。
参考
Individualは、 ‘リストのようなもの’であり、これはMutableSequenceにあたるのでこれを継承して定義してみた。
参考
性質
# deapのindividualと同じように、listに似た形で定義できる
ind0 = Individual(range(2))
ind1 = Individual(range(2, 4))
ind0
Individual([0, 1], sign=1, seed=1585548440, fitness=None)
表示もいい感じ。
# 結合できる
ind = ind0 + ind1
ind
Individual([0, 1, 2, 3], sign=1, seed=1585548440, fitness=None)
# 個人の思想でシード値を個体に持たせている
ind.seed
1585548440
# 適応度
ind.fitness
# 符号
ind.sign
1
# シード値を変更しようとするとエラー
try:
ind.seed = 1
except AttributeError as e:
print(e)
can't set attribute
# 同様に符号を変更しようとするとエラー
try:
ind.sign = 1
except AttributeError as e:
print(e)
can't set attribute
# スライスでも正しくオブジェクトが取り出せる
ind[0:2]
Individual([0, 1], sign=1, seed=1585548440, fitness=None)
# for文では、個体のもつ遺伝子が取り出せる
for i in ind:
print(i)
0
1
2
3
コメント
Pythonのlist
はMutableSequence
を継承している ‘子’ のはずなのにlist
を使わないと独自のMutableなSequenceを定義できないパラドックスに気持ち悪さを感じる。
isinstance(list(range(2)), MutableSequence)
True
有限長ならリストを使わずに定義できる (x0
, x1
, …をそれぞれOptional[T]
で可能な限り定義しておいて、それぞれを0, 1, …のindexと対応づけて__getitem__
や__setitem__
をif文を使って定義すれば実現できる) が、無限長だとそうはいかない。
この部分がPythonのリストが「おかしい」と言われる所以なのだろうか。
リストの実体は「C言語の配列」として定義されているらしいけど、MutableSequence
の ‘子’ でもあるというのはどういう状況なのだろう…。