Terrarium

白紙から描きなおすには ちょうどいいかもしれない

PythonでSwift-likeなstructを使いたいので検討(してみたがダメだった)

Pythonはいろいろなことを簡単に書けるのでよく使っています. ところが『実践Swift』を読んでみたところ,その型のある世界に憧れるようになってしまいました… (大学で型についていろいろ勉強したのもある)

www.amazon.co.jp

今回はSwift-like(と言い切ってしまっていいのか不安が残りますが)な構造体を使いたい場合にどんな実装をすれば良いのかについての検討です.

Swiftでは変数(や関数)をまとめた型としてenumstructclassprotocolなどがあります. 『実践Swift』では,structは変数のまとまりを値型として扱いたい場合に用いると書かれています.

一番簡単な例が座標情報などですね. Pythonでクラスを用いて実装すると次のようになります.

class Point():
    def __init__(self, x, y):
        self.x = x
        self.y = y

p = Point(10, 20)
print(p.x) # 10
print(p.y) # 20

一見これで良さそうに見えるのですが,インスタンス同士の代入を行なった場合クラスでは参照がコピーされます. すなわち片方の座標の値を変更するともう片方も変更されてしまいます. これは座標というただの値を表現する手法として適切ではありません.

以下がその例です.

# (上の続き)
q = p

q.x = 30

print(q.x) # 30
print(p.x) # 30

Swiftのstructを用いると,この座標を参照ではなく値そのものがコピーされるため,以上のような問題は発生しません. (そもそもC言語でも同じです) ちなみに上のコードでq = pの部分をq = copy.deepcopy(p)とするとうまくいきます(import copyが必要)が,できればq = pでコピーできてほしいものです.

すなわちPythonで値型で構造的に記述したいというのが今回の目的です.

ということをTwitterに呟いたら,enum.Enumを使うという啓示をいただきました.

早速調べると…おお…これは使えそうだ(早とちり)ということで書いてみました. 実装には次のサイトを参考にさせていただきました.

py3.hateblo.jp

import enum

class EnumTest(int, enum.Enum):
    # 継承元最初のintはvalueメソッドにおける加算にて型を決める必要があるため書く
    _x: int = 10
    _y: int = 15

    # getter を試した
    @property
    def x(self):
        return self._x
        # return self._x.value()
        # とすればうまくいくが全てのpropertyでこれをやるのは非効率
        # いい方法がないものか…?

    @classmethod
    def value(self) -> int:
        return self._x + self._y

# getterを利用(うまくいかない)
print(EnumTest.x)
# 要素を参照(名前が出てくる)
print(EnumTest._x)
# 要素の値を知る方法(ちょっとめんどい)
print(EnumTest._x.value())
# メソッドを利用(これはうまくいく)
print(EnumTest.value())

# 上から
# <property object at 0x1050a6b38>
# EnumTest._x
# 25
# 25

やりたかったようにEnumTest.xで簡単に値が取得できればよかったのですがちょっと追加の実装がいるのであまり綺麗じゃないですね.

上の実装では致命的な欠陥がいくつかあります.

  • 値の変更ができない
  • Constructorがない(座標データごとにclassを宣言する必要がある)

これではSwift-likeなstructとは離れ過ぎていますね. さらなる検討を重ねるか諦めるか…

ちなみにnamedtupleを使うとpoint.xという形で値が取得できるようですが,こちらも値を変更できないという問題があるそうです.

うーん…そもそも値型の構造体を使うよりもっとPython-likeな手法があるのかもしれないなぁ…

というわけで検討してみたがダメだったという記録です.

(追記) namedtupleが値の変更を行えないと書きましたが関数型っぽく書くなら値の変更は必要ないですね… さらにnamedtupleにメソッドを追加した例もあるのでこちらを使った方が便利そうです. まあいずれにせよスッキリとは書けないみたいだ…

Python で namedtuple を使ってバリューオブジェクトを作る | CUBE SUGAR STORAGE