見出し画像

【Python】変数への再代入を禁止する


Pythonの変数は再代入可能

Pythonでは普通に定義した変数には値の再代入が可能です。

foo: int = 33
foo = 44

# 正常終了

参考) 一方、例えば関数型言語であるScalaでは変数への値の再代入はデフォルトでできない仕様となっています。

val foo: Int = 33
foo = 44

// Reassignment to val fooエラー

再代入を禁止する方法

では、Pythonで再代入を禁止する方法について見ていきます。

大文字で定義する(単なる慣習)

FOO = 'abc'

大文字 = 定数 という共通認識に頼る方法。
もちろん、機能的に代入禁止できるわけではないです。

型ヒント(Final)を使う

from typing import Final

foo: Final[int] = 100
foo = 111

IDEなどの型チェックで警告(例: `"foo" is declared as Final and cannot be reassigned`)は出ますが、実行エラーにはなりません。
別途mypyなどの型チェックライブラリを使って、型違反があるコードをコミットに含めないようにする仕組みが必要です。


以下はクラス変数の場合です。

prefixを付ける(シングルアンダースコア)

"_(シングルアンダースコア)"が付いた変数はプライベート変数として扱われます。ただしLinterなどでチェックはしてくれますが、強制力は無いためクラス外部からのアクセスは可能です。

_foo = 100

prefixを付ける(ダブルアンダースコア)

"__(ダブルアンダースコア)"が付いた変数はマングリング機構(名前修飾)という仕組みによってクラスの内部で別の名前に置き換えられます。そのため、外部からアクセスするとエラーが発生します。
ただし、pep8では以下のように記載されており、プライベート化の用途としてこの方法を使うのは推奨されていません。

一般的には、アンダースコアを名前の先頭に二つ付けるやり方は、サブクラス化されるように設計されたクラスの属性が衝突したときに、それを避けるためだけに使うべきです。

https://pep8-ja.readthedocs.io/ja/latest/
__foo = 100

@propertyデコレータ

プロパティとして定義することで、setterやdeleter経由での値操作を強制することができます。

class C:
    def __init__(self):
        self._x = None

    @property
    def x(self):
        """I'm the 'x' property."""
        return self._x

    # @x.setter
    # def x(self, value):
    #     self._x = value

    # @x.deleter
    # def x(self):
    #     del self._x

Pydantic

最後にPydanticライブラリを使用する方法です。以下のパターン1、2いずれかの方法でイミュータブルなクラスを作成可能です。

from pydantic import BaseModel

# Pydanticのバージョン: 1.10での例

# パターン1
class Knight(BaseModel):
    title: str
    age: int
    color: str = 'blue'

    class Config:
        frozen = True
        
# パターン2      
class Knight(BaseModel, frozen=True):
    title: str
    age: int
    color: str = 'blue'

まとめ

以上、Pythonでも再代入を防止するための方法を紹介しました。
こういった方法を利用することで、バグの混入などのリスクを抑えることができそうです。


Header photo by Arnold Francisca on Unsplash

この記事が気に入ったらサポートをしてみませんか?