如何在Python中创建常量?


988

有没有办法在Python中声明常量?在Java中,我们可以按以下方式创建常量值:

public static final String CONST_NAME = "Name";

Python中上述Java常量声明的等效项是什么?


6
实际上,通过python的属性函数/装饰器可以使只读变量成为可能。该答案INV就是一个定制使用的一个例子。属性比这更通用,但是,关于它如何工作的一个很好的分析是在Shalabh Chaturvedi的Python Attributes and Methods中
2014年

20
恕我直言,强制执行恒定性不是“ pythonic”。在Python 2.7中,您甚至可以编写True=False,然后(2+2==4)==True返回False
osa

8
正如其他答案所暗示的那样,没有办法也不需要声明常量。但是您可以阅读有关约定的本PEP。例如THIS_IS_A_CONSTANT
Rasika Perera

34
@osa:您无法在python 3-中做到这一点SyntaxError: can't assign to keyword。这似乎是一件好事。
naught101

3
令人惊讶的是,到目前为止还没有提到它,但是Enums似乎是定义枚举常量的好方法。
cs95

Answers:


973

不,那里没有。您无法在Python中将变量或值声明为常量。只是不要更改它。

如果您在上课,则等效项为:

class Foo(object):
    CONST_NAME = "Name"

如果不是,那只是

CONST_NAME = "Name"

但是您可能想看看Alex Martelli 编写的Python中的代码片段Constants


从Python 3.8开始,有一个typing.Final变量注释,它将告诉静态类型检查器(如mypy)不要重新分配变量。这与Java的最接近final。但是,它实际上并不能阻止重新分配

from typing import Final

a: Final = 1

# Executes fine, but mypy will report an error if you run mypy on this:
a = 2

27
而不是执行“ Python中的常量”中的操作,而应使用“属性”函数或装饰器。
塞斯·约翰逊

26
人们要求在Perl中具有相同的功能。有一个名为“使用常量”的导入模块,但是(AFAIK)只是创建一个返回值的小函数的包装器。我在Python中也做同样的事情。示例:def MY_CONST_VALUE(): return 123
kevinarpe 2012年

8
“不,那里没有。” 没错,但是在其他人的基础上,我添加了一个答案,远远低于此答案,它是针对python 2.7(缺少“枚举”)的“常量”的简短实现。这些是类似于枚举的只读name.attribute,可以包含任何值。声明很容易Nums = Constants(ONE=1, PI=3.14159, DefaultWidth=100.0),用法很简单print 10 + Nums.PI,尝试在异常Nums.PI = 22=> ValueError(..)中更改结果。
ToolmakerSteve

108
只是不要更改它。你决定了我的一天
Hi-Angel

89
“只是不要更改它”根本没有帮助。它没有回答问题,我建议将其删除。
Bartek Banachewicz

354

没有const其他语言中的关键字,但是可以创建一个具有“ getter函数”的属性来读取数据,而没有“ setter函数”的属性来重写数据。实质上,这可以防止标识符被更改。

这是使用类属性的替代实现:

请注意,对于想知道常量的读者来说,这段代码远非易事。请参阅下面的说明

def constant(f):
    def fset(self, value):
        raise TypeError
    def fget(self):
        return f()
    return property(fget, fset)

class _Const(object):
    @constant
    def FOO():
        return 0xBAADFACE
    @constant
    def BAR():
        return 0xDEADBEEF

CONST = _Const()

print CONST.FOO
##3131964110

CONST.FOO = 0
##Traceback (most recent call last):
##    ...
##    CONST.FOO = 0
##TypeError: None

代码说明:

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

并且以其他更老式的方式:

(代码很棘手,下面有更多说明)

class _Const(object):
    @apply
    def FOO():
        def fset(self, value):
            raise TypeError
        def fget(self):
            return 0xBAADFACE
        return property(**locals())

CONST = _Const()

print CONST.FOO
##3131964110

CONST.FOO = 0
##Traceback (most recent call last):
##    ...
##    CONST.FOO = 0
##TypeError: None

请注意,@ apply装饰器似乎已被弃用。

  1. 为了定义标识符FOO,fir定义了两个函数(fset,fget-名称由我选择)。
  2. 然后使用内置property函数构造可以“设置”或“获取”的对象。
  3. 请注意,property函数的前两个参数名为fsetfget
  4. 使用我们为自己的getter和setter选择这些名字的事实,并使用应用于该范围的所有本地定义的**(双星号)创建关键字字典,以将参数传递给property函数

11
基于对文档的AttributeErrorTypeError,我认为出现的异常应该是一个新的错误,这是我提议的命名ConstantError或类似的东西,这是一个子类TypeError。在文档中让我想到的部分是:docs.python.org/2/library/exceptions.html
ArtOfWarfare 2015年

3
我对这段代码感到惊讶。为什么FOO()和BAR()方法字体以self作为参数?我的IDE在红色的括号内加了下划线(“编译”错误)。我厌倦了自我介绍,但随后出现错误。
user3770060 '16

10
这些长度的确说明了python语言的明显缺陷。他们为什么不觉得需要将其添加到Python 3中。我不敢相信没有人建议这样做,而且我根本看不到某个委员会背后的逻辑是“ nah,常量”?不。
安德鲁·S

8
并且您的解决方案仍然可以由确定的python程序员使用CONST.__dict__['FOO'] = 7
pppery

11
@OscarSmith,我认为它将改进“自记录代码”的设计。当我在代码中明确指出某些值无法更改时,要比阅读所有源代码并意识到某些值永不更改要容易理解。而且,它阻止了有人更改应该恒定的值的可能性。请记住:显式胜于隐式。
加百利

112

在Python中,人们使用命名约定(例如__method用于私有方法和_method用于受保护的方法)而不是使用语言来强制执行某些操作。

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

MY_CONSTANT = "one"

如果希望此常量永远不变,则可以加入属性访问并进行技巧,但是更简单的方法是声明一个函数

def MY_CONSTANT():
    return "one"

唯一的问题是您将必须要做MY_CONSTANT()的任何地方,但同样MY_CONSTANT = "one"是python(通常)中的正确方法。

您还可以使用namedtuple创建常量:

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

17
def MY_CONSTANT(): return "one"在代码的后面,这样做不会阻止某人这样做MY_CONSTANT = "two"(或重新声明该函数)。
Matthew Schinckel,

6
@MatthewSchinckel是关于约定的,更改MY_CONSTANT不会更改用法MY_CONSTANT(),但会引发错误,并且在python中,如果您想更改任何内容,没有任何巧妙的技巧可以保护您。
阿努拉格Uniyal

3
感谢您提出namedtuple方法。绝对创新。您可能还会发现我在这里“的评论”有关。
RayLuo

@MatthewSchinckel,您可以在python中重新定义任何内容,所以这并不是一个好主意。
cslotty

@MatthewSchinckel的想法不是写MY_CONSTANT = MY_CONSTANT(),而是MY_CONSTANT()用作常量。当然了 但这很好,并且非常符合python原则“我们都是成年人”,也就是说,当开发人员有充分的理由并且知道自己在做什么时,很少会禁止开发人员做出覆盖规则的决定。
jonathan.scholbach

69

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

class CONST(object):
    __slots__ = ()
    FOO = 1234

CONST = CONST()

# ----------

print(CONST.FOO)    # 1234

CONST.FOO = 4321              # AttributeError: 'CONST' object attribute 'FOO' is read-only
CONST.__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 = 4321
CONST.BAR = 5678

print CONST.FOO    # Still 1234!
print CONST.BAR    # Oops AttributeError

创建实例可以使magic __setattr__方法生效并拦截设置FOO变量的尝试。如果愿意,可以在这里抛出异常。通过类名称实例化实例可防止直接通过类进行访问。

一个值总让人痛苦,但是您可以将很多东西附加到您的CONST对象上。拥有上流社会的阶级名称似乎也有点古怪,但我认为总体上来说它是很简洁的。


11
这是最好,最明确的答案,因为它的“机制”最少,但功能最多。引发异常虽然很重要,但不是一个选择。
Erik Aronesty

我已经设计出了一条较短的路线,该路线会自动产生有意义的错误,但风格大致相同。我将最初的想法留在这里进行比较。
乔恩·贝茨

可惜您仍然需要这个CONST.前缀。同样在多模块情况下,这将变得复杂。
Alfe

1
我认为通常情况下,无论如何,您都希望将常量分组到一些相关的包中(而不是拥有一个巨大的CONST对象),所以这可能不是一件坏事。
乔恩·贝茨

为什么这个答案还那么遥远?!__slots__解决方案是如此优雅和有效。从我读过的所有内容来看,这与在Python中创建常量差不多。非常感谢你。对于每个感兴趣的人,这里都是对__slots__魔术的精妙而深入的解释。
JohnGalt

34

Python没有常数。

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

def MY_CONSTANT():
    return 42

MY_CONSTANT() 现在具有常量的所有功能(加上一些讨厌的花括号)。


1
我只是想添加此建议,但幸运的是,我向下滚动到评分较低的答案。我希望它将得到进一步支持,我完全同意它具有常量的所有功能,并且非常简单明了。查看所有复杂解决方案中的样板代码数量,我发现花括号相对来说并不麻烦。
yaccob

1
这是最简单的答案,尽管应该注意,它有一些开销,并且不会阻止白痴修改返回值。这只会阻止代码进一步改变源代码
MrMesees

@MrMesees修改返回值?您的意思是编辑源代码吗?但是,即使在C ++中,常量(如constexpr)是真正的硬常量,也因此不受保护。
Ruslan

@Ruslan我的意思是,由于python没有constexpr,它返回到外部上下文后不会停止对值的编辑。在此示例中,42并没有采取任何措施来强制冻结状态。
MrMesees

20

除了两个最重要的答案(仅使用带大写名称的变量,或使用属性将值设置为只读)外,我还要提到可以使用元类来实现命名常量。我提供了一个使用GitHub上的元类的非常简单的解决方案,如果您希望这些值对它们的类​​型/名称有更多的了解,这可能会有所帮助:

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

这是稍微高级些的Python,但仍然非常易于使用和方便。(该模块具有更多功能,包括常量为只读,请参见其自述文件。)

在各种存储库中都有类似的解决方案,但是据我所知,它们要么缺少我希望从常量中获得的基本特征之一(例如常量,要么是任意类型),或者它们具有深奥的特性,使它们不太适用。但是YMMV,感谢您的反馈。:-)


3
我喜欢您在GitHub上的实现。我几乎准备编写一个实现反向查找功能的基本类,但是我看到您已经做到了!
Kerr

谢谢,@ Kerr,这是我得到的第一个反馈,使我感到高兴。:-)
hans_meine

太棒了 我只是尝试了一下。很高兴将此作为选项。虽然尚未决定我是否对只读方面足够在意,但要使用它而不是简单地做def enum(**enums): return type('Enum', (), enums)Numbers = enum(ONE=1, TWO=2, THREE='three'),按照stackoverflow.com/a/1695250/199364的 “在早期版本中……”部分
ToolmakerSteve

19

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

class MyFinalProperty(object):

    @property
    def name(self):
        return "John"

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


价值不足的解决方案。我在找到此页面(不是此答案)之后才实现了该功能,然后盘旋回去添加它(如果尚未添加)。我想强调这个答案的有用性。
马克

18

编辑:添加了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 = 1
    B = 2

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

MyConst.A
# 1
my_const = MyConst()
my_const.A
# 1

MyConst.A = 'changed'
# TypeError
my_const.A = 'changed'
# TypeError

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


10

您可以使用namedtuple作为解决方法,以有效地创建一个常量,该常量的作用方式与Java中的静态最终变量(Java“常量”)相同。随着变通办法的进行,它有点优雅。(一种更优雅的方法是简单地改进Python语言---哪种语言可以让您重新定义math.pi?-但我离题了。)

(在撰写本文时,我意识到提到了namedtuple这个问题的另一个答案,但是我将继续在这里,因为我将展示一种语法,该语法与Java期望的语法更加相似,因为无需创建named 以namedtuple的类型输入)。

按照您的示例,您将记住,在Java中,我们必须在某个类中定义常量;因为您没有提到类名,所以称它为Foo。这是Java类:

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

这是等效的Python。

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

我想在这里添加的关键点是,您不需要单独的Foo类型(即使听起来像是矛盾的词,“匿名命名的元组”也很好),所以我们将我们的namedtuple命名为_Foo希望它不会转至导入模块。

这里的第二点是,我们立即创建 nametuple 的实例,将其调用Foo;无需单独执行此操作(除非您愿意)。现在,您可以执行Java中的操作:

>>> Foo.CONST_NAME
'Name'

但是您不能分配给它:

>>> Foo.CONST_NAME = 'bar'

AttributeError: can't set attribute

致谢:我以为我发明了namedtuple方法,但是后来我看到别人也给出了类似的答案(尽管不太紧凑)。然后我还注意到Python中什么是“命名元组”?,它指出sys.version_info现在是一个namedtuple,因此Python标准库也许早就提出了这个想法。

请注意,不幸的是(仍然是Python),您可以Foo完全擦除整个分配:

>>> Foo = 'bar'

(facepalm)

但是,至少我们阻止了Foo.CONST_NAME价值的改变,这总比没有好。祝好运。


感谢您提出namedtuple方法。绝对创新。您可能还会在这里找到我的“评论”
RayLuo

10

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

因此,您可以执行以下操作:

MY_CONSTANT: Final = 12407

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


9

这是“常量”类的实现,该类创建具有只读(常量)属性的实例。例如,可以使用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.PI
        print '----- 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.PI
    print '----- Following line is deliberate ValueError -----'
    Nums.PI = 22

感谢@MikeGraham的FrozenDict,我以此作为起点。已更改,因此Nums['ONE']使用语法不是Nums.ONE

并感谢@Raufio的回答,以提供覆盖__ setattr __的想法。

有关更多功能的实现,请参见GitHub上的 @Hans_meine的 named_constants


2
Python是成年人同意的语言。没有针对这种情况的保护措施。Nums._d['PI'] = 22 我相信,语言本身并没有提供任何将事物标记为非变量的方法。
Ajay M

8

从技术上讲,元组可以视为常量,因为如果尝试更改其值之一,则元组会引发错误。如果要声明具有一个值的元组,则在其唯一值后放置一个逗号,如下所示:

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

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

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

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


7

我将创建一个覆盖__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”的方式)。


+1为有趣的方法。尽管不如已经提供的答案那么干净。而且,甚至最简单的较早建议的解决方案def ONE(): return 1ONE()比此答案更易于使用ONE.value
ToolmakerSteve

7

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

文件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 -> tuple
        set -> frozenset
    Dict as value of a constant has no support.

    >>> const = Constant()
    >>> del const.temp
    Traceback (most recent call last):
    NameError: name 'temp' is not defined
    >>> const.temp = 1
    >>> const.temp = 88
    Traceback (most recent call last):
        ...
    TypeError: Constanst can not be changed
    >>> del const.temp
    Traceback (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.UNDEFINED
    Traceback (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 doctest
    doctest.testmod()

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

import decimal
import uuid
import datetime
import 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 = 1
        self.assertEqual(self.const.CONSTANT, 1)
        self.const.Constant = 2
        self.assertEqual(self.const.Constant, 2)
        self.const.ConStAnT = 3
        self.assertEqual(self.const.ConStAnT, 3)
        self.const.constant = 4
        self.assertEqual(self.const.constant, 4)
        self.const.co_ns_ta_nt = 5
        self.assertEqual(self.const.co_ns_ta_nt, 5)
        self.const.constant1111 = 6
        self.assertEqual(self.const.constant1111, 6)

    def test_create_and_change_integer_constant(self):

        self.const.INT = 1234
        self.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 = .1234
        self.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 = None
        self.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 = True
        self.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 = value
        self.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 = 1
        self.assertDictEqual(self.const(), {'A': 1})

        self.const.B = "Text"
        self.assertDictEqual(self.const(), {'A': 1, 'B': "Text"})

优点:1.可以访问整个项目的所有常量2.严格控制常量值

缺乏:1.不支持自定义类型和类型“ dict”

笔记:

  1. 经过Python3.4和Python3.5的测试(我使用的是“ tox”)

  2. 测试环境:

$ uname -a
Linux 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

您可以通过自动将字典转换为已命名的元组来稍微改善这一点
Peter Schorn

6

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

RED = 1
GREEN = 2
BLUE = 3

然后编写您的类或函数。由于常量几乎总是整数,并且在Python中也是不变的,因此更改它的机会很小。

当然,除非您明确设置RED = 2


21
是的,但是阻止 “显式设置RED = 2”的功能(在其他语言中)是能够将变量名声明为“常量”的全部好处!
ToolmakerSteve

6
阻止它会受益吗?关于const的最有用的事情通常是编译器优化,而这在Python中并不是真正的东西。想要保持不变?只是不要更改它。如果您担心其他人对其进行更改,则可以将其放在他们的范围之外,或者只是意识到,如果有人对其进行更改,那是他们的问题,他们需要处理,而不是您。
凯文(Kevin)

@Kevin:“ 您会受益吗…… ”,static为一个类的所有实例的值提供一个单一存储的好处?除非确实有可能声明静态/类变量。
分钟

8
根本问题是,有些人可能将其视为是真理的来源,无法更改,并在整个代码中将其用作真理的来源,而不是引入魔术值(我在Python中看到了很多) -其他人可能将其视为可以随意更改的内容。当有人更改了全局变量时,您无法知道更改的位置,并且应用程序由于RED =“ blue”而不是“ red”而崩溃,这是在引入一个完全不必要的问题,该问题已经解决了,因此很简单,普遍了解。
Dagrooms

5

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

class Constant:
  def __init__(self,value=None):
    self.value = value
  def __get__(self,instance,owner):
    return self.value
  def __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 Foo
class FooMeta(type): pass

# class Foo
class Foo(metaclass=FooMeta): pass

# I create constants in my metaclass
FooMeta.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

4

Python字典是可变的,因此它们似乎不是声明常量的好方法:

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

4

如果您想要常量并且不关心它们的值,这是一个技巧:

只需定义空类。

例如:

class RED: 
    pass
class BLUE: 
    pass

4

在python中,常数只是一个变量,其名称全部用大写字母表示,单词之间用下划线字符分隔,

例如

DAYS_IN_WEEK = 7

该值是可变的,因为您可以更改它。但是,鉴于名称规则告诉您一个常量,为什么呢?我的意思是,这毕竟是您的程序!

这是整个python采取的方法。没有private出于相同原因,关键字。在名称前加上下划线,您将知道该名称是私有的。代码可能会违反规则……就像程序员可以删除私有关键字一样。

Python本可以添加一个 const关键字...但是程序员可以删除关键字,然后根据需要更改常量,但是为什么这样做呢?如果您想违反规则,则可以随时更改规则。但是,如果名称使意图清楚,为什么还要烦扰规则呢?

也许在某些单元测试中,对价值进行更改有意义吗?即使在现实世界中,一周中的天数无法更改,也要查看一周8天的情况。如果这种语言阻止了您的出现,那么在这种情况下您就需要打破规则……您将不得不停止将其声明为常量,即使它在应用程序中仍然是常量,并且只是这个测试用例,查看更改后会发生什么。

所有大写的名称告诉您它应为常数。那很重要。不是一种语言会强制对代码施加约束,但是您仍然可以更改代码。

那就是python的理念。


4

没有完美的方法可以做到这一点。据我了解,大多数程序员只会将标识符大写,因此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

在这里可以找到的所有其他解决方案都是相同的,即使是聪明的解决方案也可以创建类并重新定义set属性方法,但总会有解决之道。Python就是这样。

我的建议是避免所有麻烦,只使用标识符大写。它实际上不是一个适当的常数,但是再也没有。


4

有一个更干净的方法可以使用namedtuple做到这一点:

from collections import namedtuple


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

使用范例

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

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


对于每个正在阅读本文的人,请记住,如果将可变对象设置为这些常量之一,则任何人都可以更改其内部值。例如,让bar = [1,2,3],则可以执行以下操作:CONSTS.bar [1] ='a'并且不会被拒绝。因此,请注意这一点。
Juan IgnacioSánchez

我建议不要使用Python的属性装饰器,而要采用这种有趣的方法(我只是为了好玩而已)。
胡安·伊格纳西奥·桑切斯

4

也许pconst库会为您提供帮助(github)。

$ pip install pconst

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

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


3

您可以使用StringVar或IntVar等,您的常数为const_val

val = 'Stackoverflow'
const_val = StringVar(val)
const.trace('w', reverse)

def reverse(*args):
    const_val.set(val)

2

您可以使用collections.namedtuple和进行操作itertools

import collections
import itertools
def 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.One
One
>>> print myConstants.Two
Two
>>> print myConstants.Three
Four
>>> myConstants.One = 'Two'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: can't set attribute

2

(本段的意思是对这些问题的答案评论在这里那里,里面提到namedtuple,但它变得太长,不适合转换为注释,所以,在这里它去。)

上面提到的namedtuple方法绝对是创新的。但是,为了完整起见,在其官方文档的NamedTuple部分的末尾,其内容为:

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

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

换句话说,官方文档倾向于使用一种实用的方式,而不是实际实现只读行为。我想这成为了Zen Zen的另一个例子:

简单胜于复杂。

实用性胜过纯度。


2

这是我创建的一组成语,目的是改进一些已经可用的答案。

我知道常量的使用不是pythonic,因此您不应该在家中使用它!

但是,Python是一种动态语言!该论坛展示了如何创建外观和感觉像常量的构造。该答案的主要目的是探讨语言可以表达的内容。

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

有关更多详细信息,我写了关于这些惯用语伴奏博客

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

常数空间(SpaceConstants)

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

我探索了如何使用类工厂在stackoverflow中以及在博客文章中实现基于Python的基于策略的设计。

def SpaceConstants():
    def setattr(self, name, value):
        if hasattr(self, name):
            raise AttributeError(
                "Cannot reassign members"
            )
        self.__dict__[name] = value
    cls = type('SpaceConstants', (), {
        '__setattr__': setattr
    })
    return cls()

sc = SpaceConstants()

print(sc.x) # raise "AttributeError: 'SpaceConstants' object has no attribute 'x'"
sc.x = 2 # bind attribute x
print(sc.x) # print "2"
sc.x = 3 # raise "AttributeError: Cannot reassign members"
sc.y = {'name': 'y', 'value': 2} # bind attribute y
print(sc.y) # print "{'name': 'y', 'value': 2}"
sc.y['name'] = 'yprime' # mutable object can be changed
print(sc.y) # print "{'name': 'yprime', 'value': 2}"
sc.y = {} # raise "AttributeError: Cannot reassign members"

冻结值空间(SpaceFrozenValues)

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

您必须小心使用此惯用语,因为getattr通过执行深度复制来返回缓存的值。此操作可能会对大对象产生重大的性能影响!

from copy import deepcopy

def SpaceFrozenValues():
    cache = {}
    def setattr(self, name, value):
        nonlocal cache
        if name in cache:
            raise AttributeError(
                "Cannot reassign members"
            )
        cache[name] = deepcopy(value)
    def getattr(self, name):
        nonlocal cache
        if 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 x
print(fv.x) # print "2"
fv.x = 3 # raise "AttributeError: Cannot reassign members"
fv.y = {'name': 'y', 'value': 2} # bind attribute y
print(fv.y) # print "{'name': 'y', 'value': 2}"
fv.y['name'] = 'yprime' # you can try to change mutable objects
print(fv.y) # print "{'name': 'y', 'value': 2}"
fv.y = {} # raise "AttributeError: Cannot reassign members"

常数空间(ConstantSpace)

这个习惯用法是常量变量或ConstantSpace的不变名称空间。这是赫然简单乔恩·贝茨回答的组合计算器类工厂

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 changed
print(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 object
print(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'"

2

在Python中,常数不存在,但是您可以指出变量是常数,并且不能通过添加来更改 CONST_在变量名称的开头一个变量并在注释中声明它是常量

myVariable = 0
CONST_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;

1

就我而言,我需要一个不可变的字节数组来实现一个加密库的实现,该库包含许多我想确保常量的文字数字。

此答案有效,但尝试重新分配字节数组元素不会引发错误。

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'''

    @const
    def C1():
        '''reassignment to C1 fails silently'''
        return bytearray.fromhex('deadbeef')

    @const
    def pi():
        '''is immutable'''
        return 3.141592653589793

常量是不可变的,但是常量字节数组的分配会静默失败:

>>> 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 fset
    raise ConstError
__main__.ConstError
>>> c.C1[0] = 0
>>> c.C1[0]
222
>>> c.C1
bytearray(b'\xde\xad\xbe\xef')

一种更强大,更简单,甚至可能更多的“ pythonic”方法涉及使用memoryview对象(<= 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 NotImplementedError
    elif 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 for
        read-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] = 0
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'ConstArray' object does not support item assignment
>>> C1[0]
222

1

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

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

例如:

from __future__ import print_function
from 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_RATIO
assert isinstance(magic_num, ConstFloatField)
assert isinstance(magic_num, float)

print(magic_num)  # 0.6180339887
print(magic_num.verbose_name)  # Golden Ratio

更多详细信息用法,您可以阅读pypi网址: pypigithub


1

您可以将常量包装在numpy数组中,将其标记为只写,并始终按索引零对其进行调用。

import numpy as np

# declare a constant
CONSTANT = 'hello'

# put constant in numpy and make read only
CONSTANT = np.array([CONSTANT])
CONSTANT.flags.writeable = False
# alternatively: CONSTANT.setflags(write=0)

# call our constant using 0 index    
print 'CONSTANT %s' % CONSTANT[0]

# attempt to modify our constant with try/except
new_value = 'goodbye'
try:
    CONSTANT[0] = new_value
except:
    print "cannot change CONSTANT to '%s' it's value '%s' is immutable" % (
        new_value, CONSTANT[0])

# attempt to modify our constant producing ValueError
CONSTANT[0] = new_value



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

当然,这仅保护numpy的内容,而不保护变量“ CONSTANT”本身;您仍然可以:

CONSTANT = 'foo'

CONSTANT会更改,但是第一次会快速引发TypeErrorCONSTANT[0]稍后会在脚本中调用。

虽然...我想如果您在某个时候将其更改为

CONSTANT = [1,2,3]

现在您不会再收到TypeError了。嗯...

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.