Python——实现不可修改的常量(const)

本文介绍了一种在Python中实现常量的方法,通过自定义类覆盖setattr方法,阻止对已赋值属性的重新赋值,以此达到常量的效果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

因为种种原因,Python并未提供如C/C++/Java一样的const修饰符,换言之,python中没有常量,至少截止2015年年末,还没有这个打算。Python程序一般通过约定俗成的变量名全大写的形式来表示这是一个常量,但是这终究不是长久之计。

其实Python可以曲线救国实现常量。

在Python的面向对象中,object._setattr_()这个built-in function在对类的属性赋值的时候会自动调用。其函数原型为:

object.setattr(self, name, value)
其中name为变量名,value为变量值。

而object.dict则以dict的形式保存了object内所有可写的属性,key为变量名,value为变量值。

那么我们就有可能通过建立一个const类,对其object.setattr()方法进行overwrite,在对属性值进行赋值的时候判断,如果属性存在,则表示这是对常量的重赋值操作,从而抛出异常,如果属性不存在,则表示是新声明了一个常量,可以进行赋值操作。

const.py 代码如下:

<strong># -*- coding: utf-8 -*-

class _const:
    class ConstError(TypeError) : pass

    def __setattr__(self, key, value):
        # self.__dict__
        if self.__dict__.has_key(key):
            raise self.ConstError,"constant reassignment error!"
        self.__dict__[key] = value

import sys

sys.modules[__name__] = _const()</strong>

其中,1-10行是上述思路的类的一个实现。

第12-14行的写法值得说明。我们尽管拥有了_const类,但是我们当前使用这个类仍然需要

import const

c = const._const()
c.TEST_CONSTANT = 'test'

这样的形式来声明一个常量TEST_CONSTANT,然而我们希望用更简洁的方法进行常量的赋值。形如:    

import const

const.TEST_CONSTANT = 'test'

 

在python中,name内置属性是当前的class或者type的值。通俗地讲,name的值有以下两种形式:

如果运行某一个py文件,在该文件中,name的值为’main
如果import了某一个py文件,那么在该import的文件中,name的值为该文件的文件名(不带.py后缀)
而sys.modules是一个dict对象,包括了当前上下文中python已经load的所有模块的信息,dict的key为文件名,value为模块对象。

在const.py 中,14行的写法等价于

import const

sys.modules['const'] = _const()

 

即,让_const类作为模块的入口点,引入const.py等价于声明了一个_const类的实例。

至此python的常量实现完毕,使用test.py测试:

# -*- coding: utf-8 -*-

import const

const.TEST = 'test'
print const.TEST
const.TEST1 = 'test1'
print const.TEST1
const.TEST = 'test'
print const.TEST

打印信息如下:

test test1 Traceback (most recent call last): File
“H:/code/test.py”, line 9, in
const.TEST = ‘test’ File “H:\code\const.py”, line 9, in setattr
raise self.ConstError,”constant reassigning error!” const.ConstError: constant reassignment error!

成功为两常量赋值,在试图修改第一个常量值时抛出异常:)

 

转载自:点击打开原创链接

 

 

 

 

 

 

 

 

 

 

<think>嗯,用户询问Python常量的定义及用法,这在Python中是一个有趣的话题,因为Python本身并不像其他语言那样支持真正的常量。需要根据提供的引用内容来构建一个全面的回答。用户可能之前学习过其他语言如C或Java,其中包含const或final关键字,因此对Python缺少显式常量支持感到困惑。这背后可能隐藏着两个需求:一是想了解如何在Python中模拟常量行为,二是想知道团队协作时如何确保变量不被修改的工程实践。从提供的四份引用材料来看,核心观点相当一致:Python没有内置常量类型,但可通过多种方式模拟。引用[1]和[3]都强调命名约定(全大写)是行业惯例,引用[3]和[4]则提到自定义类实现更严格的约束。需要特别注意引用[2]指出的一个细节:Python内置命名空间实际存在少量常量如True。在整理技术点时,需要把三类实现方式说得清晰:首先是基础命名约定方案,其次是模块级方案(提到__all__很有意思),最后是高级类封装方案。特别要注意提醒用户类实现方案的局限性——通过反射仍可修改,这关乎工程安全性。用户似乎更关注实用价值而非理论,所以应该强调团队协作中的命名规范价值。可以补充一个容易被忽略的点:在函数式编程中,可以用不可变数据结构(如namedtuple)来辅助实现常量效果。考虑到用户可能需要立即应用,给出的代码示例既要完整又要突出关键机制。类实现的代码中需要解释描述符协议的作用。还可以提到某些第三方库像pydantic可能提供更完善的常量方案。需要特别注明所有方法都是“君子协定”的本质,包括类包装方案实际只是添加了报错机制。这样用户才不会误解为绝对安全。</think> 在 Python 中,**语言本身并没有内置不可更改的常量类型**。这与 C(`const`关键字)或 Java(`final` 关键字)不同。Python 中的所有变量原则上都是可变的。但是,存在几种**约定俗成或自定义模拟的方式**来实现常量的效果并促进代码的可读性和可维护性: ### 1. 约定:全大写命名(最常用) * **原理:** 这是 Python 社区最广泛接受的实践。通过使用全大写字母命名变量,并向团队成员明确说明或约定该变量不应在后续代码中被修改。 * **优点:** 简单直接,符合 Python 风格(PEP 8 推荐)。清晰地向阅读代码的人传达了“这是常量”的意图。 * **缺点:** **只具提示意义,不具备强制约束力**。从语法层面讲,该变量仍然可以被程序重新赋值。 * **用法示例:** ```python # 约定:全大写变量为“常量”,不应修改 PI = 3.14159 MAX_CONNECTIONS = 100 DATABASE_URL = "postgres://user:password@localhost/dbname" # 尝试修改(语法上合法,但违背约定) PI = 3.14 # 应避免这样做!不推荐! ``` ### 2. 自定义常量模块 * **原理:** 将所有的“常量”集中定义在一个单独的模块文件(如 `config.py`, `constants.py`)中。在该模块中使用全大写命名约定。 * **优点:** * 集中管理常量,便于查找和维护。 * 导入该模块的文件通过 `import module_name` 或 `from module_name import CONST_NAME` 使用常量。 * 配合 `__all__` 变量可以控制模块导出的内容。 * 比直接在代码中使用全大写变量更规范。 * **缺点:** 同样**只有约定效力,无法阻止修改**。修改导入模块中的变量值会影响所有导入此模块的地方(但通常应禁止这种修改)。 * **用法示例 (`constants.py`):** ```python # constants.py PI = 3.14159 MAX_USERS = 1000 STATUS_ACTIVE = 'ACTIVE' __all__ = ['PI', 'MAX_USERS', 'STATUS_ACTIVE'] # 定义模块导出列表(可选) ``` ```python # main.py (使用常量) import constants area = constants.PI * radius * radius if users_count < constants.MAX_USERS: print(constants.STATUS_ACTIVE) # 或者(但修改导入的常量同样应禁止) from constants import PI, MAX_USERS print(PI) ``` ### 3. 自定义不可变常量类(更严格) * **原理:** 利用类属性和属性的特性(property, `__setattr__`, 描述符 descriptor)或枚举(`enum.Enum`)来创建行为上接近真正常量的类或对象。试图修改其值将引发异常。 * **优点:** * 提供**一定程度的运行时强制**保护,在修改时会抛出 `AttributeError` 或其他自定义异常。 * 更接近其他语言常量的行为。 * **缺点:** * 代码较复杂,需要额外编写类或使用特定模块(如 `enum`)。 * 对于简单常量有些“过重”。 * **仍非绝对安全**(例如,可以通过访问类或对象的 `__dict__` 修改,但难度大且明显是刻意破坏)。 * **常见实现方法:** * **方法一:使用属性(property)和重写`__setattr__`:** ```python class Constants: def __init__(self): super().__setattr__('PI', 3.14159) super().__setattr__('MAX_VALUE', 100) def __setattr__(self, name, value): if hasattr(self, name): raise AttributeError(f"Cannot reassign constant '{name}'") super().__setattr__(name, value) # 允许第一次定义?或者严格禁止任何修改 const = Constants() print(const.PI) # 输出: 3.14159 const.PI = 3.14 # 抛出 AttributeError: Cannot reassign constant 'PI' ``` * **方法二:使用 `enum.Enum`(适合有限个数的命名常量):** ```python from enum import Enum class Status(Enum): ACTIVE = 1 INACTIVE = 2 PENDING = 3 # 使用枚举常量 current_status = Status.ACTIVE if current_status == Status.ACTIVE: print("Active!") # 尝试修改(绝对不允许) Status.ACTIVE = 4 # 抛出 AttributeError: Cannot reassign members. ``` * **方法三:使用命名元组`collections.namedtuple`(创建的实例不可变):** ```python from collections import namedtuple Constants = namedtuple('Constants', ['PI', 'MAX']) const = Constants(PI=3.14159, MAX=100) print(const.PI) # 输出: 3.14159 const.PI = 3.14 # 抛出 AttributeError: can't set attribute ``` ### 总结与最佳实践 1. **根本事实:** Python 本身没有内建的、语法级别的常量机制。 2. **最推荐方案:** **使用全大写字母的命名约定**。这是 Python 生态系统的标准做法(见 PEP 8),简单高效。通过代码审查和团队规范来确保不被修改。 3. **管理多个常量:** 推荐使用**自定义常量模块**(一个 `.py` 文件存放所有常量)。 4. **需要运行时保护:** 对于极少数确实需要防止意外修改的场景(或作为库作者),可考虑**自定义常量类**(利用 `property`, `__setattr__`, 描述符)或使用 **`enum.Enum`**(适用于有限枚举值)。 5. **核心要点:** Python 中的“常量”本质上是一个强烈的编程**约定和意图声明**,旨在由开发者自觉遵守并在团队协作中得到保障,而非由编译器或解释器强制执行[^1][^3][^4]。内建命名空间中的 `True`、`False`、`None` 是语言定义的、行为不可变的特殊实例[^2]。 **简单来说,在 Python 中定义“常量”的常规做法就是用全大写字母命名变量(如 `MY_CONSTANT`),并且约定在后续代码中不应该去修改它的值[^1][^3][^4]。** 更严格的保护需要额外的自定义代码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值