0
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Pythonで定数を宣言したい(n番煎じ)

Last updated at Posted at 2021-03-17

2021-03-18追記

さすがに適当に書きすぎてたので、もうちょっとちゃんと書き直しました。
下記のコードは継承周りでバグがあったので、**ここ**にあるコードを使ってください。

TL;DR

  • [1] Constantクラスを定義する(下記のコードコピペ).
class ConstantError(Exception):
    """Constantクラスの例外"""
    pass

class MetaConstant(type):
    """Constantクラスのメタクラス"""

    # __init__関数置き換え用の関数
    def _meta__init__(self, *args, **kwargs):
        raise ConstantError("Can't make instance of Constant class")

    def __new__(cls, classname, bases, dict):
        # 定数クラスのインスタンス生成を禁止
        dict["__init__"] = cls._meta__init__

        # 継承時の定数再定義を禁止
        const_names = [name for name in dict if not name.startswith("__")]
        base_consts = set(*[base.__dict__ for base in bases])
        for name in const_names:
            if name in base_consts:
                raise ConstantError(f"Can't rebind constant [{name}]")

        return type.__new__(cls, classname, bases, dict)

    def __setattr__(self, name, value):
        if name.startswith("__"):
            super(MetaConstant, self).__setattr__(name, value)
        else:
            raise ConstantError(f"Can't set attribute to Constant [{name}]")

class Constant(metaclass=MetaConstant):
    """定数クラス"""
    pass
  • [2] Constantを継承したクラスを宣言する。
class MyConstant(Constant):
    """サンプルの定数クラス

    Attributes
    ----------
    FOO : int

    methods
    -------
    twice_of_FOO
    """

    # クラス変数として定数を定義する
    FOO: int = 1

    # クラスメソッドも定義できる
    @classmethod
    def twice_of_FOO(cls):
        """FOOの二倍を返す関数

        Returns
        -------
        int : 2*MyConstant.FOO
        """
        return 2*cls.FOO

# 定義した定数クラスのサブクラスも作れる
# (サブクラスから親クラスの定数も参照できる)
class MySubConstant(MyConstant):
    """サンプルの定数クラス

    Attributes
    ----------
    FOO : int (by MyConstant)
    BAR : int
    """
    BAR: int = 2


# 参照は通常のクラス変数と同じように参照する
print(f"MyConstant.FOO = {MyConstant.FOO}")
print(f"MyConstant.twice_of_FOO() = {MyConstant.twice_of_FOO()}")
print(f"MySubConstant.FOO = {MySubConstant.FOO}")
print(f"MySubConstant.BAR = {MySubConstant.BAR}")

# 代入はできない
MyConstant.FOO = 2 # ConstantError

# あとから追加も不可
MyConstant.BAZ = 3 # ConstantError

# ただし例外的に"__"(アンダースコア2個)から始まるものは代入・変更OK
# 用法用量を守って正しく使ってね
MyConstant.__BAZ = 3
print(f"MyConstant.__BAZ = {MyConstant.__BAZ}")

# インスタンスの生成ができない
constant_instance = MyConstant() # ConstantError

モチベーション

こことかのコードは使えるは使えるんだが、あとから定数を付け足せてしまったり、参照可能な定数を切り分けられない辺りにもんやりしてしまっていた。
ので、n番煎じではありますが、定数クラス作ってみることにしました。

仕様

とりあえず、仕様としては下記のとおりとした。

  1. クラス宣言時のみ、定数の宣言ができる(あとから付け足し不可)
  2. 定数クラスのインスタンス生成は禁止
  3. 継承関係を持たせることで、参照範囲を切り分け可

出来たのが上記のコードですが、正直ここまで書いといて「(いや絶対探せばどっかにあるやん)」ってなってます。
これよりエレガントなコードあったら教えてくださいお願いします。

ちなみに

見る人が見ればわかると思いますが、ミュータブルなデータ(辞書型とか)は、再代入不可でも操作はできるので、中身は変わったりします。
なので、イミュータブルなデータのみにするか、イミュータブルな辞書を使ったりしましょう。

参考にさせていただいた偉大な情報

0
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?