如何在Python中创建常量?

如何在Python中声明常量?

在Java,我们:

public static final String CONST_NAME = "Name";
1345202 次浏览

您不能在Python中将变量或值声明为常量。


为了表明告诉程序员变量是常量,人们通常用大写字母写它:

CONST_NAME = "Name"

要在常量更改时引发异常,请参阅Alex Martelli的Python中的常量。请注意,这在实践中并不常用。


从Python 3.8开始,有一个#0变量注释会告诉静态类型检查器(如mypy)您的变量不应该被重新分配。这与Java的final最接近。然而,它并不能阻止重新分配

from typing import Final
a: Final[int] = 1
# Executes fine, but mypy will report an error if you run mypy on this:a = 2

在Python中,人们使用命名约定而不是语言强制执行某些东西,例如私人方法使用__method保护方法使用_method

因此,以同样的方式,您可以简单地将常量声明为所有大写,例如:

MY_CONSTANT = "one"

如果你希望这个常量永远不会改变,你可以连接到属性访问并做一些技巧,但更简单的方法是声明一个函数:

def MY_CONSTANT():return "one"

唯一的问题是你必须做MY_CONSTANT()的地方,但MY_CONSTANT = "one"在Python中是正确的方式(通常)。


您还可以使用命名元组创建常量:

>>> from collections import namedtuple>>> Constants = namedtuple('Constants', ['pi', 'e'])>>> constants = Constants(3.14, 2.718)>>> constants.pi3.14>>> constants.pi = 3Traceback (most recent call last):File "<stdin>", line 1, in <module>AttributeError: can't set attribute

声明“常量”的Pythonic方式基本上是一个模块级变量:

RED = 1GREEN = 2BLUE = 3

然后编写你的类或函数。由于常量几乎总是整数,并且它们在Python中也是不可变的,所以你改变它的机会很小。

当然,除非您显式设置RED = 2

没有其他语言中的const关键字,但是可以创建一个属性,有一个“getter函数”读取数据,没有“setter函数”重写数据。这基本上保护了标识符不被更改。

这是一个使用class属性的替代实现:

请注意,对于想了解常量的读者来说,这段代码并不容易。请参阅下面的解释。

def constant(f):def fset(self, value):raise TypeErrordef fget(self):return f()return property(fget, fset)
class _Const(object):@constantdef FOO():return 0xBAADFACE@constantdef BAR():return 0xDEADBEEF
CONST = _Const()
print(hex(CONST.FOO))  # -> '0xbaadfaceL'
CONST.FOO = 0##Traceback (most recent call last):##  File "example1.py", line 22, in <module>##    CONST.FOO = 0##  File "example1.py", line 5, in fset##    raise TypeError##TypeError

代码解释:

  1. 定义一个函数constant,它接受一个表达式,并使用它来构造一个“getter”——一个仅返回表达式值的函数。
  2. setter函数引发TypeError,因此它是只读的
  3. 使用我们刚刚创建的constant函数作为装饰来快速定义只读属性。

以另一种更老式的方式:

(代码相当棘手,下面有更多解释)

class _Const(object):def FOO():def fset(self, value):raise TypeErrordef fget(self):return 0xBAADFACEreturn property(**locals())FOO = FOO()  # Define property.
CONST = _Const()
print(hex(CONST.FOO))  # -> '0xbaadfaceL'
CONST.FOO = 0##Traceback (most recent call last):##  File "example2.py", line 16, in <module>##    CONST.FOO = 0##  File "example2.py", line 6, in fset##    raise TypeError##TypeError
  1. 为了定义标识符FOO,fis定义了两个函数(fset、fget——名称由我选择)。
  2. 然后使用内置的property函数构造一个可以“设置”或“获取”的对象。
  3. 注意,property函数的前两个参数分别命名为fsetfget
  4. 使用我们为我们自己的getter和setter选择这些名称的事实,并使用应用于该范围的所有本地定义的**(双星号)创建一个关键字字典,以将参数传递给property函数

Python字典是可变的,所以它们看起来不是声明常量的好方法:

>>> constants = {"foo":1, "bar":2}>>> print constants{'foo': 1, 'bar': 2}>>> constants["bar"] = 3>>> print constants{'foo': 1, 'bar': 3}

我会创建一个类来覆盖基对象类的__setattr__方法并用它包装我的常量,注意我使用的是python 2.7:

class const(object):def __init__(self, val):super(const, self).__setattr__("value", val)def __setattr__(self, name, val):raise ValueError("Trying to change a constant value", self)

要包装字符串:

>>> constObj = const("Try to change me")>>> constObj.value'Try to change me'>>> constObj.value = "Changed"Traceback (most recent call last):...ValueError: Trying to change a constant value>>> constObj2 = const(" or not")>>> mutableObj = constObj.value + constObj2.value>>> mutableObj #just a string'Try to change me or not'

这很简单,但是如果你想像使用非常量对象一样使用常量(不使用constObj.value),它会更加密集。这可能会导致问题,所以最好保留.value以显示并知道你正在使用常量进行操作(尽管可能不是最“Pythonic”的方式)。

扩展Raufio的答案,添加__repr__以返回值。

class const(object):def __init__(self, val):super(const, self).__setattr__("value", val)def __setattr__(self, name, val):raise ValueError("Trying to change a constant value", self)def __repr__(self):return ('{0}'.format(self.value))
dt = const(float(0.01))print dt

然后对象的行为更像你可能期望的那样,你可以直接访问它,而不是'. value'

除了两个最重要的答案(只需使用带有大写名称的变量,或使用属性使值只读)之外,我想提到可以使用元类来实现命名常量。我在github中提供了一个使用元类的非常简单的解决方案,如果你想让值更多地了解它们的类型/名称,这可能会有所帮助:

>>> from named_constants import Constants>>> class Colors(Constants):...     black = 0...     red = 1...     white = 15...>>> c = Colors.black>>> c == 0True>>> cColors.black>>> c.name()'black'>>> Colors(0) is cTrue

这是稍微高级一点的Python,但仍然非常易于使用和方便。(该模块还有更多功能,包括常量为只读,请参阅其README。)

在各种存储库中也有类似的解决方案,但据我所知,它们要么缺乏我对常量的期望的基本特征之一(比如常量,或者是任意类型的),要么它们添加了一些深奥的特征,使它们不太普遍适用。但是YMMV,我将不胜感激的反馈。:-)

这是一个“常量”类的实现,它创建具有只读(常量)属性的实例。例如。可以使用Nums.PI获取已初始化为3.14159的值,而Nums.PI = 22引发异常。

# ---------- Constants.py ----------class Constants(object):"""Create objects with read-only (constant) attributes.Example:Nums = Constants(ONE=1, PI=3.14159, DefaultWidth=100.0)print 10 + Nums.PIprint '----- Following line is deliberate ValueError -----'Nums.PI = 22"""
def __init__(self, *args, **kwargs):self._d = dict(*args, **kwargs)
def __iter__(self):return iter(self._d)
def __len__(self):return len(self._d)
# NOTE: This is only called if self lacks the attribute.# So it does not interfere with get of 'self._d', etc.def __getattr__(self, name):return self._d[name]
# ASSUMES '_..' attribute is OK to set. Need this to initialize 'self._d', etc.#If use as keys, they won't be constant.def __setattr__(self, name, value):if (name[0] == '_'):super(Constants, self).__setattr__(name, value)else:raise ValueError("setattr while locked", self)
if (__name__ == "__main__"):# Usage example.Nums = Constants(ONE=1, PI=3.14159, DefaultWidth=100.0)print 10 + Nums.PIprint '----- Following line is deliberate ValueError -----'Nums.PI = 22

多亏了@MikeGraham的视频,我用它作为起点。更改,所以用法语法不是Nums['ONE']而是Nums.ONE

感谢@Raufio的回答,为了覆盖__setattr__。

或者更多功能的实现,请参见@Hans_meinenamed_constants在GitHub

我最近发现了一个非常简洁的更新,它会自动引发有意义的错误消息并阻止通过__dict__访问:

class CONST(object):__slots__ = ()FOO = 1234
CONST = CONST()
# ----------
print(CONST.FOO)    # 1234
CONST.FOO = 4321              # AttributeError: 'CONST' object attribute 'FOO' is read-onlyCONST.__dict__['FOO'] = 4321  # AttributeError: 'CONST' object has no attribute '__dict__'CONST.BAR = 5678              # AttributeError: 'CONST' object has no attribute 'BAR'

我们将自己定义为使自己成为一个实例,然后使用插槽来确保不能添加额外的属性。这也删除了__dict__访问路由。当然,整个对象仍然可以重新定义。

编辑-原始解决方案

我可能错过了一个技巧,但这似乎对我有用:

class CONST(object):FOO = 1234
def __setattr__(self, *_):pass
CONST = CONST()
#----------
print CONST.FOO    # 1234
CONST.FOO = 4321CONST.BAR = 5678
print CONST.FOO    # Still 1234!print CONST.BAR    # Oops AttributeError

创建实例允许魔术__setattr__方法启动并拦截设置FOO变量的尝试。如果您愿意,您可以在此处抛出异常。在类名上实例化实例会阻止直接通过类访问。

对于一个值来说,这是一个完全的痛苦,但是你可以在你的CONST对象上附加很多。有一个上层类,类名也看起来有点糟糕,但我认为它总体上相当简洁。

好吧…即使这已经过时了,让我在这里加上我的2美分:-)

class ConstDict(dict):def __init__(self, *args, **kwargs):super(ConstDict, self).__init__(*args, **kwargs)
def __setitem__(self, key, value):if key in self:raise ValueError("Value %s already exists" % (key))super(ConstDict, self).__setitem__(key, value)

而不是ValueError打破,您可以防止那里发生任何更新。这样做的一个优点是您可以在程序中动态添加常量,但一旦设置了常量,您就不能更改。此外,您可以在设置常量之前添加任何规则或任何东西(例如key必须是字符串或小写字符串或大写字符串等)在设置键之前)

但是,我认为在Python中设置常量没有任何重要性。没有优化可以像C中那样发生,因此我想这是不需要的。

在我的例子中,我需要不可变字节数组来实现一个包含许多我想确保恒定的文字数字的加密库。

这个答案可以工作,但尝试重新分配byterix元素不会引发错误。

def const(func):'''implement const decorator'''def fset(self, val):'''attempting to set a const raises `ConstError`'''class ConstError(TypeError):'''special exception for const reassignment'''pass
raise ConstError
def fget(self):'''get a const'''return func()
return property(fget, fset)

class Consts(object):'''contain all constants'''
@constdef C1():'''reassignment to C1 fails silently'''return bytearray.fromhex('deadbeef')
@constdef pi():'''is immutable'''return 3.141592653589793

常量是不可变的,但常量byterix赋值会静默失败:

>>> c = Consts()>>> c.pi = 6.283185307179586  # (https://en.wikipedia.org/wiki/Tau_(2%CF%80))Traceback (most recent call last):File "<stdin>", line 1, in <module>File "consts.py", line 9, in fsetraise ConstError__main__.ConstError>>> c.C1[0] = 0>>> c.C1[0]222>>> c.C1bytearray(b'\xde\xad\xbe\xef')

一种更强大、更简单、甚至更“pythonic”的方法涉及使用内存视图对象(<=python-2.6中的缓冲区对象)。

import sys
PY_VER = sys.version.split()[0].split('.')
if int(PY_VER[0]) == 2:if int(PY_VER[1]) < 6:raise NotImplementedErrorelif int(PY_VER[1]) == 6:memoryview = buffer
class ConstArray(object):'''represent a constant bytearray'''def __init__(self, init):'''create a hidden bytearray and expose a memoryview of that bytearray forread-only use'''if int(PY_VER[1]) == 6:self.__array = bytearray(init.decode('hex'))else:self.__array = bytearray.fromhex(init)
self.array = memoryview(self.__array)
def __str__(self):return str(self.__array)
def __getitem__(self, *args, **kwargs):return self.array.__getitem__(*args, **kwargs)

ConstArray项赋值为TypeError

>>> C1 = ConstArray('deadbeef')>>> C1[0] = 0Traceback (most recent call last):File "<stdin>", line 1, in <module>TypeError: 'ConstArray' object does not support item assignment>>> C1[0]222

编辑:为Python 3添加了示例代码

注意:另一个答案看起来提供了一个更完整的实现,类似于以下内容(具有更多功能)。

首先,创建一个元类

class MetaConst(type):def __getattr__(cls, key):return cls[key]
def __setattr__(cls, key, value):raise TypeError

这可以防止更改静态属性。然后创建另一个使用该元类的类:

class Const(object):__metaclass__ = MetaConst
def __getattr__(self, name):return self[name]
def __setattr__(self, name, value):raise TypeError

或者,如果您使用Python 3:

class Const(object, metaclass=MetaConst):def __getattr__(self, name):return self[name]
def __setattr__(self, name, value):raise TypeError

这应该可以防止实例道具被更改。要使用它,请继承:

class MyConst(Const):A = 1B = 2

现在,直接或通过实例访问的props应该是恒定的:

MyConst.A# 1my_const = MyConst()my_const.A# 1
MyConst.A = 'changed'# TypeErrormy_const.A = 'changed'# TypeError

这是上面的一个例子。这是 Python 3的另一个例子。

我为python const编写了一个util lib:kkconst-pypi支持str, int,浮点数, datetime

const字段实例将保留其基类型行为。

例如:

from __future__ import print_functionfrom kkconst import (BaseConst,ConstFloatField,)
class MathConst(BaseConst):PI = ConstFloatField(3.1415926, verbose_name=u"Pi")E = ConstFloatField(2.7182818284, verbose_name=u"mathematical constant")  # Euler's number"GOLDEN_RATIO = ConstFloatField(0.6180339887, verbose_name=u"Golden Ratio")
magic_num = MathConst.GOLDEN_RATIOassert isinstance(magic_num, ConstFloatField)assert isinstance(magic_num, float)
print(magic_num)  # 0.6180339887print(magic_num.verbose_name)  # Golden Ratio

更多详细用法您可以阅读pypi URL: pypi github

从技术上讲,元组是一个常量,因为如果你尝试更改它的一个值,元组会引发错误。如果你想声明一个只有一个值的元组,然后在它唯一的值后面放一个逗号,如下所示:

my_tuple = (0 """Or any other value""",)

要检查此变量的值,请使用类似于以下内容:

if my_tuple[0] == 0:#Code goes here

如果您尝试更改此值,将引发错误。

您可以使用命名元组作为变通方法来有效地创建一个常量,该常量的工作方式与Java中的静态最终变量(Java“常量”)相同。作为变通方法,它有点优雅。(更优雅的方法是简单地改进Python语言-什么样的语言可以让您重新定义math.pi?-但我离题了。)

(当我写这篇文章时,我意识到这个问题的另一个答案提到了namedtuple,但我将在这里继续,因为我将展示一个与您在Java中期望的更接近的语法,因为没有必要创建一个命名类型,因为namedtuple强制您这样做。

按照你的例子,你会记得在Java我们必须定义常量在某个班级里;因为你没有提到类名,让我们称之为Foo。这是Java类:

public class Foo {public static final String CONST_NAME = "Name";}

这是等效的Python。

from collections import namedtupleFoo = namedtuple('_Foo', 'CONST_NAME')('Name')

我想在这里补充的关键点是,你不需要一个单独的Foo类型(“匿名命名元组”会很好,即使这听起来像是矛盾的),所以我们命名我们的namedtuple_Foo,希望它不会逃逸到导入模块。

这里的第二点是我们的名称组立即创建实例,称其为Foo;没有必要在单独的步骤中执行此操作(除非您愿意)。现在您可以执行Java中可以执行的操作:

>>> Foo.CONST_NAME'Name'

但是你不能给它赋值:

>>> Foo.CONST_NAME = 'bar'…AttributeError: can't set attribute

致谢:我以为我发明了命名元组方法,但后来我看到其他人给出了类似的(尽管不那么紧凑)答案。然后我还注意到Python中的“命名元组”是什么?,它指出#0现在是一个命名元组,所以也许Python标准库早些时候就已经提出了这个想法。

请注意,不幸的是(这仍然是Python),您可以完全删除整个Foo赋值:

>>> Foo = 'bar'

(facepalm)

但至少我们防止了Foo.CONST_NAME值被更改,这总比什么都没有好。祝你好运。

不幸的是,Python还没有常量,这是一种耻辱。ES6已经向JavaScript添加了支持常量(https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Statements/const),因为它在任何编程语言中都是非常有用的东西。正如Python社区中其他答案所回答的那样,使用约定用户大写变量作为常量,但它不能防止代码中的任意错误。如果您愿意,您可能会发现有用的单文件解决方案如下(请参阅docstring如何使用它)。

文件constants.py

import collections

__all__ = ('const', )

class Constant(object):"""Implementation strict constants in Python 3.
A constant can be set up, but can not be changed or deleted.Value of constant may any immutable type, as well as list or set.Besides if value of a constant is list or set, it will be converted in an immutable type as next:list -> tupleset -> frozensetDict as value of a constant has no support.
>>> const = Constant()>>> del const.tempTraceback (most recent call last):NameError: name 'temp' is not defined>>> const.temp = 1>>> const.temp = 88Traceback (most recent call last):...TypeError: Constanst can not be changed>>> del const.tempTraceback (most recent call last):...TypeError: Constanst can not be deleted>>> const.I = ['a', 1, 1.2]>>> print(const.I)('a', 1, 1.2)>>> const.F = {1.2}>>> print(const.F)frozenset([1.2])>>> const.D = dict()Traceback (most recent call last):...TypeError: dict can not be used as constant>>> del const.UNDEFINEDTraceback (most recent call last):...NameError: name 'UNDEFINED' is not defined>>> const(){'I': ('a', 1, 1.2), 'temp': 1, 'F': frozenset([1.2])}"""
def __setattr__(self, name, value):"""Declaration a constant with value. If mutable - it will be converted to immutable, if possible.If the constant already exists, then made prevent againt change it."""
if name in self.__dict__:raise TypeError('Constanst can not be changed')
if not isinstance(value, collections.Hashable):if isinstance(value, list):value = tuple(value)elif isinstance(value, set):value = frozenset(value)elif isinstance(value, dict):raise TypeError('dict can not be used as constant')else:raise ValueError('Muttable or custom type is not supported')self.__dict__[name] = value
def __delattr__(self, name):"""Deny against deleting a declared constant."""
if name in self.__dict__:raise TypeError('Constanst can not be deleted')raise NameError("name '%s' is not defined" % name)
def __call__(self):"""Return all constans."""
return self.__dict__

const = Constant()

if __name__ == '__main__':import doctestdoctest.testmod()

如果这还不够,请参阅完整的测试用例。

import decimalimport uuidimport datetimeimport unittest
from ..constants import Constant

class TestConstant(unittest.TestCase):"""Test for implementation constants in the Python"""
def setUp(self):
self.const = Constant()
def tearDown(self):
del self.const
def test_create_constant_with_different_variants_of_name(self):
self.const.CONSTANT = 1self.assertEqual(self.const.CONSTANT, 1)self.const.Constant = 2self.assertEqual(self.const.Constant, 2)self.const.ConStAnT = 3self.assertEqual(self.const.ConStAnT, 3)self.const.constant = 4self.assertEqual(self.const.constant, 4)self.const.co_ns_ta_nt = 5self.assertEqual(self.const.co_ns_ta_nt, 5)self.const.constant1111 = 6self.assertEqual(self.const.constant1111, 6)
def test_create_and_change_integer_constant(self):
self.const.INT = 1234self.assertEqual(self.const.INT, 1234)with self.assertRaisesRegexp(TypeError, 'Constanst can not be changed'):self.const.INT = .211
def test_create_and_change_float_constant(self):
self.const.FLOAT = .1234self.assertEqual(self.const.FLOAT, .1234)with self.assertRaisesRegexp(TypeError, 'Constanst can not be changed'):self.const.FLOAT = .211
def test_create_and_change_list_constant_but_saved_as_tuple(self):
self.const.LIST = [1, .2, None, True, datetime.date.today(), [], {}]self.assertEqual(self.const.LIST, (1, .2, None, True, datetime.date.today(), [], {}))
self.assertTrue(isinstance(self.const.LIST, tuple))
with self.assertRaisesRegexp(TypeError, 'Constanst can not be changed'):self.const.LIST = .211
def test_create_and_change_none_constant(self):
self.const.NONE = Noneself.assertEqual(self.const.NONE, None)with self.assertRaisesRegexp(TypeError, 'Constanst can not be changed'):self.const.NONE = .211
def test_create_and_change_boolean_constant(self):
self.const.BOOLEAN = Trueself.assertEqual(self.const.BOOLEAN, True)with self.assertRaisesRegexp(TypeError, 'Constanst can not be changed'):self.const.BOOLEAN = False
def test_create_and_change_string_constant(self):
self.const.STRING = "Text"self.assertEqual(self.const.STRING, "Text")
with self.assertRaisesRegexp(TypeError, 'Constanst can not be changed'):self.const.STRING += '...'
with self.assertRaisesRegexp(TypeError, 'Constanst can not be changed'):self.const.STRING = 'TEst1'
def test_create_dict_constant(self):
with self.assertRaisesRegexp(TypeError, 'dict can not be used as constant'):self.const.DICT = {}
def test_create_and_change_tuple_constant(self):
self.const.TUPLE = (1, .2, None, True, datetime.date.today(), [], {})self.assertEqual(self.const.TUPLE, (1, .2, None, True, datetime.date.today(), [], {}))
with self.assertRaisesRegexp(TypeError, 'Constanst can not be changed'):self.const.TUPLE = 'TEst1'
def test_create_and_change_set_constant(self):
self.const.SET = {1, .2, None, True, datetime.date.today()}self.assertEqual(self.const.SET, {1, .2, None, True, datetime.date.today()})
self.assertTrue(isinstance(self.const.SET, frozenset))
with self.assertRaisesRegexp(TypeError, 'Constanst can not be changed'):self.const.SET = 3212
def test_create_and_change_frozenset_constant(self):
self.const.FROZENSET = frozenset({1, .2, None, True, datetime.date.today()})self.assertEqual(self.const.FROZENSET, frozenset({1, .2, None, True, datetime.date.today()}))
with self.assertRaisesRegexp(TypeError, 'Constanst can not be changed'):self.const.FROZENSET = True
def test_create_and_change_date_constant(self):
self.const.DATE = datetime.date(1111, 11, 11)self.assertEqual(self.const.DATE, datetime.date(1111, 11, 11))
with self.assertRaisesRegexp(TypeError, 'Constanst can not be changed'):self.const.DATE = True
def test_create_and_change_datetime_constant(self):
self.const.DATETIME = datetime.datetime(2000, 10, 10, 10, 10)self.assertEqual(self.const.DATETIME, datetime.datetime(2000, 10, 10, 10, 10))
with self.assertRaisesRegexp(TypeError, 'Constanst can not be changed'):self.const.DATETIME = None
def test_create_and_change_decimal_constant(self):
self.const.DECIMAL = decimal.Decimal(13123.12312312321)self.assertEqual(self.const.DECIMAL, decimal.Decimal(13123.12312312321))
with self.assertRaisesRegexp(TypeError, 'Constanst can not be changed'):self.const.DECIMAL = None
def test_create_and_change_timedelta_constant(self):
self.const.TIMEDELTA = datetime.timedelta(days=45)self.assertEqual(self.const.TIMEDELTA, datetime.timedelta(days=45))
with self.assertRaisesRegexp(TypeError, 'Constanst can not be changed'):self.const.TIMEDELTA = 1
def test_create_and_change_uuid_constant(self):
value = uuid.uuid4()self.const.UUID = valueself.assertEqual(self.const.UUID, value)
with self.assertRaisesRegexp(TypeError, 'Constanst can not be changed'):self.const.UUID = []
def test_try_delete_defined_const(self):
self.const.VERSION = '0.0.1'with self.assertRaisesRegexp(TypeError, 'Constanst can not be deleted'):del self.const.VERSION
def test_try_delete_undefined_const(self):
with self.assertRaisesRegexp(NameError, "name 'UNDEFINED' is not defined"):del self.const.UNDEFINED
def test_get_all_defined_constants(self):
self.assertDictEqual(self.const(), {})
self.const.A = 1self.assertDictEqual(self.const(), {'A': 1})
self.const.B = "Text"self.assertDictEqual(self.const(), {'A': 1, 'B': "Text"})

优点:1.全项目所有常量的访问权限2.严格控制常量的值

缺少:1.不支持自定义类型和“字典”类型

备注:

  1. 使用Python3.4和Python3.5进行测试(我正在使用'tox')

  2. 测试环境:

.

$ uname -aLinux wlysenko-Aspire 3.13.0-37-generic #64-Ubuntu SMP Mon Sep 22 21:28:38 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux

您可以在下一个类的帮助下模拟常量变量。用法示例:

# Constconst = Const().add(two=2, three=3)
print 'const.two: ', const.twoprint 'const.three: ', const.three
const.add(four=4)
print 'const.four: ', const.four
#const.four = 5 # a error here: four is a constant
const.add(six=6)
print 'const.six: ', const.six
const2 = Const().add(five=5) # creating a new namespace with Const()print 'const2.five: ', const2.five#print 'const2.four: ', const2.four # a error here: four does not exist in const2 namespace
const2.add(five=26)

当您想启动一个新的常量命名空间时调用构造函数。请注意,当Martelli的const类不是时,该类是受到保护,来自意外的修改序列类型常量。

来源在下面。

from copy import copy
class Const(object):"A class to create objects with constant fields."
def __init__(self):object.__setattr__(self, '_names', [])

def add(self, **nameVals):for name, val in nameVals.iteritems():if hasattr(self, name):raise ConstError('A field with a name \'%s\' is already exist in Const class.' % name)
setattr(self, name, copy(val)) # set up getter
self._names.append(name)
return self

def __setattr__(self, name, val):if name in self._names:raise ConstError('You cannot change a value of a stored constant.')
object.__setattr__(self, name, val)

您可以将常量包装在numpy数组中,标记它仅写,并始终通过索引零调用它。

import numpy as np
# declare a constantCONSTANT = 'hello'
# put constant in numpy and make read onlyCONSTANT = np.array([CONSTANT])CONSTANT.flags.writeable = False# alternatively: CONSTANT.setflags(write=0)
# call our constant using 0 indexprint 'CONSTANT %s' % CONSTANT[0]
# attempt to modify our constant with try/exceptnew_value = 'goodbye'try:CONSTANT[0] = new_valueexcept:print "cannot change CONSTANT to '%s' it's value '%s' is immutable" % (new_value, CONSTANT[0])
# attempt to modify our constant producing ValueErrorCONSTANT[0] = new_value


>>>CONSTANT hellocannot change CONSTANT to 'goodbye' it's value 'hello' is immutableTraceback (most recent call last):File "shuffle_test.py", line 15, in <module>CONSTANT[0] = new_valueValueError: assignment destination is read-only

当然,这只保护numpy的内容,而不是变量“CONSTANT”本身;你仍然可以这样做:

CONSTANT = 'foo'

CONSTANT会改变,但是这会在脚本中第一次调用CONSTANT[0]时迅速抛出TypeError。

不过…我想如果你把它改成

CONSTANT = [1,2,3]

现在你不会再得到TypeError了。

https://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.setflags.html

我们可以创建一个描述符对象。

class Constant:def __init__(self,value=None):self.value = valuedef __get__(self,instance,owner):return self.valuedef __set__(self,instance,value):raise ValueError("You can't change a constant")

1)如果我们想在实例级别使用常量,那么:

class A:NULL = Constant()NUM = Constant(0xFF)
class B:NAME = Constant('bar')LISTA = Constant([0,1,'INFINITY'])
>>> obj=A()>>> print(obj.NUM)  #=> 255>>> obj.NUM =100
Traceback (most recent call last):File "<stdin>", line 1, in <module>ValueError: You can't change a constant

2)如果我们只想在类级别创建常量,我们可以使用元类作为我们的常量(我们的描述符对象)的容器;所有下降的类都将继承我们的常量(我们的描述符对象),没有任何可以修改的风险。

# metaclass of my class Fooclass FooMeta(type): pass
# class Fooclass Foo(metaclass=FooMeta): pass
# I create constants in my metaclassFooMeta.NUM = Constant(0xff)FooMeta.NAME = Constant('FOO')
>>> Foo.NUM   #=> 255>>> Foo.NAME  #=> 'FOO'>>> Foo.NUM = 0 #=> ValueError: You can't change a constant

如果我创建了Foo的子类,这个类将继承常量而不会修改它们

class Bar(Foo): pass
>>> Bar.NUM  #=> 255>>> Bar.NUM = 0  #=> ValueError: You can't change a constant

属性是创建常量的一种方法。您可以通过声明getter属性来做到这一点,但忽略setter。例如:

class MyFinalProperty(object):
@propertydef name(self):return "John"

您可以查看我写的一篇文章以找到更多使用Python属性的方法。

如果你想要常量而不关心它们的值,这里有一个技巧:

只需定义空类。

e. g:

class RED:passclass BLUE:pass

Python没有常量。

也许最简单的选择是为它定义一个函数:

def MY_CONSTANT():return 42

MY_CONSTANT()现在具有常量的所有功能(加上一些烦人的大括号)。

您可以使用StringVar或IntVar等,您的常量是const_val

val = 'Stackoverflow'const_val = StringVar(val)const.trace('w', reverse)
def reverse(*args):const_val.set(val)

您可以使用collections.namedtupleitertools

import collectionsimport itertoolsdef Constants(Name, *Args, **Kwargs):t = collections.namedtuple(Name, itertools.chain(Args, Kwargs.keys()))return t(*itertools.chain(Args, Kwargs.values()))
>>> myConstants = Constants('MyConstants', 'One', 'Two', Three = 'Four')>>> print myConstants.OneOne>>> print myConstants.TwoTwo>>> print myConstants.ThreeFour>>> myConstants.One = 'Two'Traceback (most recent call last):File "<stdin>", line 1, in <module>AttributeError: can't set attribute

在Python中,常量不存在,但您可以通过在变量名的开头添加CONST_并在注释中声明它是常量来指示变量是常量并且不得更改:

myVariable = 0CONST_daysInWeek = 7    # This is a constant - do not change its value.CONSTANT_daysInMonth = 30 # This is also a constant - do not change this value.

或者,您可以创建一个像常量一样的函数:

def CONST_daysInWeek():return 7;

在python中,常量只是一个变量,名称大写,单词由下划线分隔,

例如

DAYS_IN_WEEK=7

值是可变的,就像你可以改变它一样。但是考虑到名称告诉你是常量的规则,你为什么要这样做?我的意思是,这毕竟是你的程序!

这是整个python采用的方法。出于同样的原因,没有private关键字。在名称前面加上下划线,你就知道它是私有的。代码可以打破规则……就像程序员无论如何都可以删除私有关键字一样。

Python本可以添加一个const关键字……但是程序员可以删除关键字,然后根据需要更改常量,但是为什么要这样做呢?如果你想打破规则,你可以改变规则。但是如果名字让意图变得清晰,为什么还要打破规则呢?

也许有一些单元测试,在那里对值应用更改是有意义的?看看一周8天会发生什么,即使在现实世界中一周中的天数不能改变。如果语言阻止了你做出例外,如果只有这一种情况,你需要打破规则…你将不得不停止将其声明为常量,即使它在应用程序中仍然是常量,只有这一个测试用例可以看到如果它被改变会发生什么。

全大写名称告诉您它旨在成为一个常量。这才是重要的。不是一种强制约束代码的语言,您无论如何都有能力更改。

这就是python的哲学。

您只需:

STRING_CONSTANT = "hi"NUMBER_CONSTANT = 89

希望这能让一切变得简单

没有完美的方法可以做到这一点。据我所知,大多数程序员只会将标识符大写,因此PI=3.142可以很容易地理解为一个常量。

另一方面,如果你想要一个真正像常量一样的东西,我不确定你是否能找到它。无论你做什么,总会有某种编辑“常量”的方法,所以它不会真的是一个常量。这是一个非常简单、肮脏的例子:

def define(name, value):if (name + str(id(name))) not in globals():globals()[name + str(id(name))] = value
def constant(name):return globals()[name + str(id(name))]
define("PI",3.142)
print(constant("PI"))

这看起来像是一个PHP风格的常量。

实际上,某人更改值所需要的就是:

globals()["PI"+str(id("PI"))] = 3.1415

这对于你在这里找到的所有其他解决方案都是一样的-即使是创建类并重新定义集合属性方法的聪明解决方案-总会有办法绕过它们。这就是Python的方式。

我的建议是避免所有的麻烦,只是大写你的标识符。它不会真的是一个合适的常量,但又没有什么。

(这一段是对这些答案这里那里的评论,其中提到了namedtuple,但它太长了,无法融入评论,所以,在这里。

上面提到的namedtuple方法绝对是创新的。不过,为了完整起见,在官方留档的NamedTuple部分的末尾,它写道:

枚举常量可以用命名元组实现,但使用简单的类声明更简单、更有效:

class Status:open, pending, closed = range(3)

换句话说,官方留档更喜欢使用实用的方式,而不是实际实现只读行为。我想这是Python之禅的另一个例子:

简洁胜于复杂。

实用性胜过纯度。

有一种更清晰的方法来使用namedtuple:

from collections import namedtuple

def make_consts(name, **kwargs):return namedtuple(name, kwargs.keys())(**kwargs)

用法示例

CONSTS = make_consts("baz1",foo=1,bar=2)

通过这种精确的方法,您可以为常量命名空间。

也许pconst库会帮助你(github)。

$ pip install pconst

from pconst import constconst.APPLE_PRICE = 100const.APPLE_PRICE = 200

[Out] Constant value of "APPLE_PRICE" is not editable.

这是我创建的习语集合,试图改进一些已经可用的答案。

我知道使用常数不是pythonic,你不应该在家里这样做!

然而,Python是一种动态语言!本论坛展示了如何创建看起来和感觉像常量的构造。这个答案的主要目的是探索语言可以表达什么。

请不要对我太苛刻:-)。

对于更多细节,我写了一个关于这些习语的伴奏博客

在这篇文章中,我将调用一个常量变量作为对值(不可变或其他)的常量引用。此外,当一个变量引用一个客户端代码无法更新其值的可变对象时,我说它有一个冻结的值。

一个常量空间(Space常量)

这个习惯用法创建了一个看起来像常量变量的命名空间(又名Space常量)。它是Alex Martelli对代码片段的修改,以避免使用模块对象。特别是,这种修改使用了我所说的类厂,因为在常量函数中,定义了一个名为常量的类,并返回了它的一个实例。

我在stackoverflowblogpost中探索了使用类厂在Python中实现基于策略的设计外观。

def SpaceConstants():def setattr(self, name, value):if hasattr(self, name):raise AttributeError("Cannot reassign members")self.__dict__[name] = valuecls = type('SpaceConstants', (), {'__setattr__': setattr})return cls()
sc = SpaceConstants()
print(sc.x) # raise "AttributeError: 'SpaceConstants' object has no attribute 'x'"sc.x = 2 # bind attribute xprint(sc.x) # print "2"sc.x = 3 # raise "AttributeError: Cannot reassign members"sc.y = {'name': 'y', 'value': 2} # bind attribute yprint(sc.y) # print "{'name': 'y', 'value': 2}"sc.y['name'] = 'yprime' # mutable object can be changedprint(sc.y) # print "{'name': 'yprime', 'value': 2}"sc.y = {} # raise "AttributeError: Cannot reassign members"

冻结值的空间(SpaceFrozenValue)

下一个习惯用法是对常量的修改,其中引用的可变对象被冻结。这个实现利用了我在setattrgetattr函数之间所说的共享闭包。可变对象的值被函数共享闭包内的变量缓存定义复制和引用。它形成了我所说的可变对象的闭包保护副本

使用此习惯用法时必须小心,因为getattr通过深度复制返回缓存的值。此操作可能会对大型对象产生显着的性能影响!

from copy import deepcopy
def SpaceFrozenValues():cache = {}def setattr(self, name, value):nonlocal cacheif name in cache:raise AttributeError("Cannot reassign members")cache[name] = deepcopy(value)def getattr(self, name):nonlocal cacheif name not in cache:raise AttributeError("Object has no attribute '{}'".format(name))return deepcopy(cache[name])cls = type('SpaceFrozenValues', (),{'__getattr__': getattr,'__setattr__': setattr})return cls()
fv = SpaceFrozenValues()print(fv.x) # AttributeError: Object has no attribute 'x'fv.x = 2 # bind attribute xprint(fv.x) # print "2"fv.x = 3 # raise "AttributeError: Cannot reassign members"fv.y = {'name': 'y', 'value': 2} # bind attribute yprint(fv.y) # print "{'name': 'y', 'value': 2}"fv.y['name'] = 'yprime' # you can try to change mutable objectsprint(fv.y) # print "{'name': 'y', 'value': 2}"fv.y = {} # raise "AttributeError: Cannot reassign members"

常量空间(ConstantSpace)

这个习惯用法是常量变量或属性空间的不可变命名空间。它是非常简单的Jon Betts在stackoverflow中的答案与类厂的组合。

def ConstantSpace(**args):args['__slots__'] = ()cls = type('ConstantSpace', (), args)return cls()
cs = ConstantSpace(x = 2,y = {'name': 'y', 'value': 2})
print(cs.x) # print "2"cs.x = 3 # raise "AttributeError: 'ConstantSpace' object attribute 'x' is read-only"print(cs.y) # print "{'name': 'y', 'value': 2}"cs.y['name'] = 'yprime' # mutable object can be changedprint(cs.y) # print "{'name': 'yprime', 'value': 2}"cs.y = {} # raise "AttributeError: 'ConstantSpace' object attribute 'x' is read-only"cs.z = 3 # raise "AttributeError: 'ConstantSpace' object has no attribute 'z'"

冰冻空间(FrozenSpace)

这个习惯用法是冻结变量或FrozenSpace的不可变命名空间。它源自前面的模式,使每个变量成为生成的FrozenSpace类的关闭保护财产

from copy import deepcopy
def FreezeProperty(value):cache = deepcopy(value)return property(lambda self: deepcopy(cache))
def FrozenSpace(**args):args = {k: FreezeProperty(v) for k, v in args.items()}args['__slots__'] = ()cls = type('FrozenSpace', (), args)return cls()
fs = FrozenSpace(x = 2,y = {'name': 'y', 'value': 2})
print(fs.x) # print "2"fs.x = 3 # raise "AttributeError: 'FrozenSpace' object attribute 'x' is read-only"print(fs.y) # print "{'name': 'y', 'value': 2}"fs.y['name'] = 'yprime' # try to change mutable objectprint(fs.y) # print "{'name': 'y', 'value': 2}"fs.y = {} # raise "AttributeError: 'FrozenSpace' object attribute 'x' is read-only"fs.z = 3 # raise "AttributeError: 'FrozenSpace' object has no attribute 'z'"

PEP 591有“最终”限定符。强制执行取决于类型检查器。

所以你可以这样做:

MY_CONSTANT: Final = 12407

备注:Final关键字仅适用于Python 3.8版本

from enum import Enumclass StringConsts(str,Enum):ONE='one'TWO='two'
print(f'Truth is  {StringConsts.ONE=="one"}') #Truth is TrueStringConsts.ONE="one" #Error: Cannot reassign

Enum和str的这种混合使您不必重新实现setattr(通过Enum)和与其他str对象(通过str)的比较。

这可能会完全弃用http://code.activestate.com/recipes/65207-constants-in-python/?in=user-97991

我不禁提供了我自己的非常轻极简主义metaclass实现(它可能显示为前一个元类答案的变体)。

常量存储在容器类(无需实例化)中。值可以设置一次,但一旦设置就不能更改(或删除)。

就我个人而言,我目前没有这个用例,但这是一个有趣的练习。

class MetaConstant(type):''' Metaclass that allows underlying class to store constants at class-level (subclass instance not needed).Non-existent attributes of underlying class (constants) can be set initially, but cannot be changed or deleted.'''
def __setattr__(klass, attr, value):'If attribute (constant) doesn\'t exist, set value. If attribute exists, raise AttributeError.'if hasattr(klass, attr):raise AttributeError(f'Can\'t change the value of the constant {klass.__name__}.{attr} to {value}'f' (the value of {klass.__name__}.{attr} is already set to'f' {getattr(klass, attr)}).')super().__setattr__(attr, value)
def __delattr__(klass, attr):if hasattr(klass, attr):raise AttributeError(f'Can\'t delete constant {klass.__name__}.{attr}'f' (set to {getattr(klass, attr)}).')

class Constants(metaclass=MetaConstant):'Container class for constants. No instantiation required.'#pass               # uncomment if no constants set upon class creationB = 'Six'           # sets Constants.B to 'Six'

Constants.B = 6         # AttributeErrordel Constants.B         # AttributeError
Constants.A = 'Five'    # sets Constants.A to 'Five'Constants.A = 5         # AttributeErrordel Constants.A         # AttributeError

随时提出改进建议。

所有给出的答案基本上有两种类型:

  1. 创建某种对象,您可以创建一旦定义就无法更改的属性。
  2. 使用约定(例如在所有大写中编写常量,或者对于Python 3.8,使用final限定符到表明,您希望一个或多个名称成为常量。

它们可以总结为“你不能用Python做你要求的事情”。

但是,实际上有一种方法可以创建具有真正常量的模块。这样做的代码相当复杂,我只会概述需要做什么,因为它已经在开源许可下可用。

  1. 使用导入挂钩来启用自定义模块的创建。我为此使用的通用代码是在这里找到
  2. 创建一个特殊的dict,它只允许添加一次符合您选择的模式的项目(例如,所有大写的名称),并防止此类名称的值发生更改。为此,您需要定义自己的方法,例如__setitem____delitem__等。这种字典(例如发现在这个文件,超过250行)的代码大约100行长。
  3. 普通Python模块的dict是不能修改的。因此,在创建模块时,您需要先执行特殊字典中的代码,然后使用其内容来更新模块的字典。
  4. 为了防止从模块外部修改内容的值(即monkeypatch),您可以使用重新定义的__setattr____delattr__方法将模块的__class__替换为自定义值。

关于这个例子的留档可以在这里找到。它可能应该更新以反映对这个问题给出的答案数量。

我知道这是一个老问题,但由于仍在添加新的解决方案,我想使可能的解决方案列表更加完整。

class ConstantError(Exception):pass  # maybe give nice error message
class AllowConstants:_constants = None_class_constants = None
def __init__(self):self._constants = {}if self._class_constants is not None:self._constants.update(self._class_constants)
def constant(self, name, value):assert isinstance(name, str)assert self._constants is not None, "AllowConstants was not initialized"if name in self._constants or name in self.__dict__:raise ConstantError(name)self._constants[name] = value
def __getattr__(self, attr):if attr in self._constants:return self._constants[attr]raise AttributeError(attr)
def __setattr__(self, attr, val):if self._constants is None:# not finished initializationself.__dict__[attr] = valelse:if attr in self._constants:raise ConstantError(attr)else:self.__dict__[attr] = val
def __dir__(self):return super().__dir__() + list(self._constants.keys())

子类化此时,您创建的常量将受到保护:

class Example(AllowConstants):def __init__(self, a, b):super().__init__()self.constant("b", b)self.a = a
def try_a(self, value):self.a = value
def try_b(self, value):self.b = value
def __str__(self):return str({"a": self.a, "b": self.b})
def __repr__(self):return self.__str__()

example = Example(1, 2)print(example)  # {'a': 1, 'b': 2}
example.try_a(5)print(example)  # {'a': 5, 'b': 2}
example.try_b(6)  # ConstantError: b
example.a = 7print(example)  # {'a': 7, 'b': 2}
example.b = 8  # ConstantError: b
print(hasattr(example, "b"))  # True
#  To show that constants really do immediately become constant:
class AnotherExample(AllowConstants):def __init__(self):super().__init__()self.constant("a", 2)print(self.a)self.a=3

AnotherExample()  # 2  ConstantError: a

# finally, for class constants:class YetAnotherExample(Example):_class_constants = {'BLA': 3}
def __init__(self, a, b):super().__init__(a,b)
def try_BLA(self, value):self.BLA = value
ex3 = YetAnotherExample(10, 20)ex3.BLA  # 3ex3.try_BLA(10)  # ConstantError: BLAex3.BLA = 4  # ConstantError: BLA

常量是本地的(从AllowCon的类继承的每个实例都有自己的常量),只要它们没有被重新分配,就可以充当普通属性,并且编写继承自此的类允许或多或少与支持常量的语言相同的样式。

此外,如果你想阻止任何人通过直接访问实例来更改值。_constants,你可以使用其他答案中建议的不允许这样做的许多容器之一。最后,如果你真的觉得有必要,你可以阻止人们设置所有实例。_constants通过一些更多的属性访问AllowCon的新字典。(当然,这些都不是非常Pythonic的,但这不是重点)。

编辑(因为使python unpythonic是一个有趣的游戏):为了使继承更容易一点,你可以修改Allow常量如下:

class AllowConstants:_constants = None_class_constants = None
def __init__(self):self._constants = {}self._update_class_constants()
def __init_subclass__(cls):"""Without this, it is necessary to set _class_constants in any subclass of any class that has class constants"""if cls._class_constants is not None:#prevent trouble where _class_constants is not overwrittenpossible_cases = cls.__mro__[1:-1] #0 will have cls and -1 will have objectfor case in possible_cases:if cls._class_constants is case._class_constants:cls._class_constants = Nonebreak
def _update_class_constants(self):"""Help with the inheritance of class constants"""for superclass in self.__class__.__mro__:if hasattr(superclass, "_class_constants"):sccc = superclass._class_constantsif sccc is not None:for key in sccc:if key in self._constants:raise ConstantError(key)self._constants.update(sccc)
def constant(self, name, value):assert isinstance(name, str)assert self._constants is not None, "AllowConstants was not initialized"if name in self._constants or name in self.__dict__:raise ConstantError(name)self._constants[name] = value
def __getattr__(self, attr):if attr in self._constants:return self._constants[attr]raise AttributeError(attr)
def __setattr__(self, attr, val):if self._constants is None:# not finished initializationself.__dict__[attr] = valelse:if attr in self._constants:raise ConstantError(attr)else:self.__dict__[attr] = val
def __dir__(self):return super().__dir__() + list(self._constants.keys())

这样你就可以做:

class Example(AllowConstants):_class_constants = {"BLA": 2}def __init__(self, a, b):super().__init__()self.constant("b", b)self.a = a
def try_a(self, value):self.a = value
def try_b(self, value):self.b = value
def __str__(self):return str({"a": self.a, "b": self.b})
def __repr__(self):return self.__str__()

class ChildExample1(Example):_class_constants = {"BLI": 88}

class ChildExample2(Example):_class_constants = {"BLA": 44}

example = ChildExample1(2,3)print(example.BLA)  # 2example.BLA = 8  # ConstantError BLAprint(example.BLI)  # 88example.BLI = 8  # ConstantError BLI
example = ChildExample2(2,3)  # ConstantError BLA

我正在尝试不同的方法在Python中创建一个真正的常量,也许我找到了漂亮的解决方案。

示例:

为常量创建容器

>>> DAYS = Constants(...     MON=0,...     TUE=1,...     WED=2,...     THU=3,...     FRI=4,...     SAT=5,...     SUN=6... )

从容器中获取价值

>>> DAYS.MON0>>> DAYS['MON']0

用纯python数据结构表示

>>> list(DAYS)['WED', 'SUN', 'FRI', 'THU', 'MON', 'TUE', 'SAT']>>> dict(DAYS){'WED': 2, 'SUN': 6, 'FRI': 4, 'THU': 3, 'MON': 0, 'TUE': 1, 'SAT': 5}

所有常量都是不可变的

>>> DAYS.MON = 7...AttributeError: Immutable attribute
>>> del DAYS.MON...AttributeError: Immutable attribute

仅对常量自动完成

>>> dir(DAYS)['FRI', 'MON', 'SAT', 'SUN', 'THU', 'TUE', 'WED']

排序像list.sort

>>> DAYS.sort(key=lambda (k, v): v, reverse=True)>>> list(DAYS)['SUN', 'SAT', 'FRI', 'THU', 'WED', 'TUE', 'MON']

可复制python2python3

简单的常量容器

from collections import OrderedDictfrom copy import deepcopy
class Constants(object):"""Container of constant"""
__slots__ = ('__dict__')
def __init__(self, **kwargs):
if list(filter(lambda x: not x.isupper(), kwargs)):raise AttributeError('Constant name should be uppercase.')
super(Constants, self).__setattr__('__dict__',OrderedDict(map(lambda x: (x[0], deepcopy(x[1])), kwargs.items())))
def sort(self, key=None, reverse=False):super(Constants, self).__setattr__('__dict__',OrderedDict(sorted(self.__dict__.items(), key=key, reverse=reverse)))
def __getitem__(self, name):return self.__dict__[name]
def __len__(self):return  len(self.__dict__)
def __iter__(self):for name in self.__dict__:yield name
def keys(self):return list(self)
def __str__(self):return str(list(self))
def __repr__(self):return '<%s: %s>' % (self.__class__.__name__, str(self.__dict__))
def __dir__(self):return list(self)
def __setattr__(self, name, value):raise AttributeError("Immutable attribute")
def __delattr__(*_):raise AttributeError("Immutable attribute")

您可以将Tuple用于常量变量:

元组是一个有序的集合,不变

my_tuple = (1, "Hello", 3.4)print(my_tuple[0])

在Python中创建常量的更好方法是从优秀的attrs库,这有助于Python程序员创建没有样板的类。The做空骗局包执行相同的操作常量通过提供方便的包装器attr.make_class.[免责声明:我是短骗局的作者]

值可以通过dictkwargs显式声明:这些示例做同样的事情。constants()函数支持cons()是一个简单的基于kwarg的使用的助手。

from short_con import constants, cons
Pieces = constants('Pieces', dict(king = 0, queen = 9, rook = 5, bishop = 3, knight = 3, pawn = 1))Pieces = cons('Pieces', king = 0, queen = 9, rook = 5, bishop = 3, knight = 3, pawn = 1)

对于值与(或可以从中派生)相同的情况属性名称,用法更加紧凑。只需提供名称作为以空格分隔的字符串、列表或元组。

NAMES = 'KING QUEEN ROOK BISHOP KNIGHT PAWN'xs = NAMES.split()
Pieces = constants('Pieces', NAMES)      # All of these do the same thing.Pieces = constants('Pieces', xs)Pieces = constants('Pieces', tuple(xs))

基于名称的用法支持一些样式约定:大写或小写属性名称,以及枚举样式的值。

底层值是可以直接访问的,这与内置enum库:

Pieces.QUEEN        # short-con usagePieces.QUEEN.value  # enum library usage

并且该对象可直接迭代并可转换为其他集合:

for name, value in Pieces:print(name, value)
d = dict(Pieces)tups = list(Pieces)

注意:这是一个糟糕的想法和一个糟糕的实现。此外,它只适用于最后的小示例,完整的实现将意味着大量的工作,我懒得做。此外,审计挂钩在Python 3.8之前可能不可用。

我主要回答了另一个问题,事实证明它与这个有关。这个想法是,你可以利用审计挂钩来捕获每一行的执行,解析代码对象,如果它填充了一些条件(例如某个前缀并且已经定义过一次),你可以抛出错误。

您可能必须支持其他赋值类型(例如,对于导入的东西,可能对于函数内部的本地变量,解包等),而不是使用globals,因为可以很容易地修改该字典,实际上调查这是否安全,接受这个实现将对您的整个应用程序造成的性能损失,确保这在REPL之外工作,在ipython内部,等等。

>>> import sys>>> import ast>>> import dis>>> import types>>>>>>>>> def hook(name, tup):...     if name == "exec" and tup:...         if tup and isinstance(tup[0], types.CodeType):...             code = tup[0]...             store_instruction_arg = None...             instructions = [dis.opname[op] for op in code.co_code]......             for i, instruction in enumerate(instructions):...                 if instruction == "STORE_NAME":...                     store_instruction_arg = code.co_code[i + 1]...                     break......             if store_instruction_arg is not None:...                 var_name = code.co_names[store_instruction_arg]...                 if var_name in globals():...                     raise Exception("Cannot re-assign variable")...>>>>>> sys.addaudithook(hook)>>>>>> a = '123'>>> a = 456Traceback (most recent call last):File "<stdin>", line 16, in hookException: Cannot re-assign variable>>>>>> a'123'

如果你最终以这种方式结束,你不应该这样做,除了修复和泛化代码之外,你可能想找到一种方法来只使一些东西保持不变,例如,只有那些具有特殊前缀的对象或只有具有一些注释的对象。

这不是完全恒定的,但从python 3.7开始,您可以使用数据类模块,如下所示:

from dataclasses import dataclassfrom typing import Final
@dataclass(frozen=True)class A():a1:Final = 3
a = A()
a.a1 = 4
---------------------------------------------------------------------------FrozenInstanceError                       Traceback (most recent call last)<ipython-input-14-5f7f4efc5bf0> in <module>----> 1 a.a1 = 4
<string> in __setattr__(self, name, value)
FrozenInstanceError: cannot assign to field 'a1'

我使用冻结的数据类声明常量值,如下所示:

from dataclasses import dataclass
@dataclass(frozen=True)class _Const:SOME_STRING = 'some_string'SOME_INT = 5    
Const = _Const()
# In another file import Const and tryprint(Const.SOME_STRING)  # ITS OK!Const.SOME_INT = 6  # dataclasses.FrozenInstanceError: cannot assign to field 'SOME_INT'