如何在Python中创建常量?

1366

在Python中如何声明常量?

在Java中,我们这样做:

public static final String CONST_NAME = "Name";

11
在Python中,通过使用property函数/装饰器,可以实现将变量设置为只读。inv答案是它的一个自定义用例示例。虽然property函数更具有通用性,但关于它的工作原理的良好分析可在Shalabh Chaturvedi的Python Attributes and Methods网站上找到。 - n611x007
35
在我看来,强制使用常量 "不符合 Python 的风格"。在 Python 2.7 中,甚至可以编写 True=False,然后 (2+2==4)==True 将返回 False - Sergey Orshanskiy
8
正如其他答案所建议的那样,没有必要或者说没有办法去声明常量。但是你可以阅读这个PEP来了解惯例。例如:THIS_IS_A_CONSTANT。 - Govinnage Rasika Perera
51
@osa: 在 Python 3 中你不能这样做 - SyntaxError: can't assign to keyword。这看起来是一件好事。 - naught101
10
直到现在都没有提到,但是枚举似乎是定义枚举常量的一种好方式。 - cs95
显示剩余2条评论
44个回答

1345

在Python中,您无法将变量或值声明为常量。


为了向程序员表明一个变量是常量,通常会将其用大写字母表示:

CONST_NAME = "Name"

要在常量被更改时引发异常,请参见Alex Martelli的Python中的常量。请注意,这在实践中并不常用。
自Python 3.8起,有一个typing.Final变量注释,可以告诉静态类型检查器(如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

在emacs中,mypy未对:Final的重新赋值给出任何注释。我需要进行任何配置设置吗? - alper
85
初学者的建议:查找为什么不可变性是一种代码质量机制。对于认为Python缺少常量没有问题的高级程序员,也请这样做。 - Arnaud Meuret
请注意,回答中提到的是2010年的情况。但自从Python 3.4于2014年发布以来,答案应该是“你需要使用enum”,它允许您设置一个不可变的可选值(因为有两种类型的常量:(1)用作稳定键的常量,在这种情况下实际值无关紧要:常量本身就是值,(2)用于存储固定值的常量)。 - Mike 'Pomax' Kamermans
@ArnaudMeuret - 不可变性是我认为Haskell是有史以来最伟大的编程语言之一的原因之一 https://mmhaskell.com/blog/2017/1/9/immutability-is-awesome; 不幸的是,往往很难得到老板的许可使用Haskell... - Shawn Eary
1
@ShawnEary 不仅仅是Haskell,但我同意不可变性的重要性。 - undefined
显示剩余2条评论

437

在其他语言中没有像常量一样的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(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. 设置函数引发TypeError错误,因此只读
  3. 使用我们刚创建的constant函数作为装饰器,快速定义只读属性。

还有一种更老式的方式:

(这段代码相当棘手,请参见下文的更多解释)

class _Const(object):
    def FOO():
        def fset(self, value):
            raise TypeError
        def fget(self):
            return 0xBAADFACE
        return 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,首先定义两个函数(fset、fget - 名称由我选择)。
  2. 然后使用内置的property函数构造一个对象,该对象可以进行“设置”或“获取”操作。
  3. 注意property函数的前两个参数分别命名为fsetfget
  4. 利用我们选择这些名称作为自己的getter和setter,并使用** (双星号) 应用于该作用域中所有局部定义来创建关键字字典,以将参数传递给property函数。

如果我需要一个常量uuid uuuid.uuid4().hex,该怎么办?这个属性将防止uuid函数改变,但是值会改变。 - Jurakin
如果你想要在一个共同的名称下收集只读结构,namedtuple不是一种更简单的实现吗? - Gerhard
如果你想要一个以共同名称为基础的只读构造集合,namedtuple 不是一个更简单的实现吗? - undefined
这太棒了,而且非常容易集成,多亏了这个类,我们可以创建不同的常量集,比如 ERRORS = _Errors() 等等。 - undefined

159
在Python中,与其强制要求某些内容,人们更多地使用命名约定,例如使用__method来表示私有方法,使用_method来表示受保护的方法。
同样的方式,您可以将常量声明为全部大写,例如:
MY_CONSTANT = "one"

如果您希望这个常量永远不变,可以钩入属性访问并进行一些技巧处理,但更简单的方法是声明一个函数:
def MY_CONSTANT():
    return "one"

唯一的问题是您需要在每个地方都执行MY_CONSTANT(),但是在Python中MY_CONSTANT =“one”通常是正确的方式。


您还可以使用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

7
使用def MY_CONSTANT(): return "one"并不能防止方法引用被重新赋值,对吗?这难道不正是鸭子类型的工作原理吗? - Josh Gust
如果您希望此常量永远不会改变 - 这是常量的默认属性。 - milosmns
1
constants.pi = 3 失败了,但是 constants = Constants(3, 2) 没有。 - Maxim Egorushkin

106

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

我最近发现了一种非常简洁的更新方法,可以自动生成有意义的错误信息并防止通过__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

创建实例允许魔法方法__setattr__生效并拦截设置FOO变量的尝试。如果需要,您可以在这里抛出异常。通过类名而非直接访问类来实例化实例可以防止直接访问。

对于一个值来说这是一种完全的痛苦,但是您可以将很多值附加到CONST对象上。拥有一个上层类名似乎也有点不好看,但总体上我认为它相当简洁。


52

Python 没有常量。

或许最简单的替代方法是为其定义一个函数:

def MY_CONSTANT():
    return 42

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


4
我本来只是想提出这个建议,但很幸运我往下滚了一下看到了评分不高的答案。我希望它能够得到更多的赞同,而且我完全同意它具有常量的所有功能,而且非常简单明了。在看到所有复杂解决方案中的样板代码数量时,我发现大括号相对不那么烦人。 - yaccob
1
这是最简单的答案,尽管应该注意它有一些开销,并且不能阻止白痴修改返回值。它只会防止后面的代码更改源代码。 - MrMesees
@MrMesees修改返回值?你是指编辑源代码吗?但即使在C++中,常量(如constexpr)也是真正的硬常量,因此您仍然无法受到保护。 - Ruslan
1
@Ruslan 我的意思是,由于 Python 没有 constexpr ,因此它不会阻止在将值返回到外部上下文后对其进行编辑。在这个示例中,没有对 42 进行任何操作来强制其保持冻结状态。 - MrMesees
2
你可以在Python中重新声明函数,所以这并没有真正解决任何问题。 - kaan_a
显示剩余3条评论

34

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

class MyFinalProperty(object):

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

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


1
低估值解决方案。我在找到这个页面(而不是这个答案)后刚刚实施了它,如果还没有添加,我会回来补充的。我想强调这个答案的实用性。 - Marc
2
绝对是一个被低估的解决方案。我浏览了整个答案列表,想知道为什么没有人考虑这个简单的解决方案。在我看来,这是唯一合理的解决方案:它非常简单和方便。然而,所有的答案都表明,在Python中没有常量是一个糟糕的设计决策。 - Regis May
不错。但是你的链接已经失效了。 - Eric Duminil

21

除了两个最佳答案(只需使用大写名称的变量或使用属性使值为只读),我想提到还可以使用元类来实现命名常量。我在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,但仍然非常易于使用和方便。 (该模块具有一些其他功能,包括常量为只读,请参阅其README。)

在各种存储库中都有类似的解决方案,但据我所知,它们要么缺少我从常量中期望的基本功能之一(例如是常量或任意类型),要么添加了奇特的功能,使它们的适用性不如此通用。但你的情况可能不同,我会感激您的反馈。 :-)


19

编辑:已添加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的另一个示例。


17

PEP 591有“final”限定符。执法由类型检查器负责。

因此,您可以执行:

MY_CONSTANT: Final = 12407

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


2
Final 类型是通用的,如果您已经进行类型检查以强制执行此操作,则可能会对示例中的未输入使用感到不满。我认为这种用法的正确示例应该是: MY_CONSTANT: Final[int] = 12407 - Dave Birch
1
根据该PEP,原始语句应该是正确的:“类型检查器应该应用其通常的类型推断机制来确定ID的类型(在这里,可能是int)。”。@DaveBirch - Sherlock70

14

我使用冻结数据类声明常量值,像这样:

from dataclasses import dataclass

@dataclass(frozen=True)
class _Const:
    SOME_STRING = 'some_string'
    SOME_INT = 5
    
Const = _Const()

# In another file import Const and try
print(Const.SOME_STRING)  # ITS OK!
Const.SOME_INT = 6  # dataclasses.FrozenInstanceError: cannot assign to field 'SOME_INT'

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接