如何在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个回答

5

如果您需要常量但不关心其值,这里有一个技巧:

只需定义空类。

例如:

class RED: 
    pass
class BLUE: 
    pass

5

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

我的建议是尽量避免麻烦,直接将标识符大写。虽然它不会真正成为一个合适的常量,但它也算得上是一个。


5

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

示例:

创建常量容器

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

从容器中获取值
>>> DAYS.MON
0
>>> 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 OrderedDict
from 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")


4

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

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

4
也许 pconst 库可以帮到您(github)。 $ pip install pconst
from pconst import const
const.APPLE_PRICE = 100
const.APPLE_PRICE = 200

[输出] "APPLE_PRICE" 的常量值不可编辑。

4
(This paragraph was meant to be a comment on those answers here and there, which mentioned namedtuple, but it is getting too long to be fit into a comment, so, here it goes.)
上面提到的namedtuple方法绝对是创新的。为了完整起见,在其官方文档的NamedTuple部分结尾处,它写道:
(The NamedTuple section of its official documentation)

enumerated constants can be implemented with named tuples, but it is simpler and more efficient to use a simple class declaration:

class Status:
    open, pending, closed = range(3)
换句话说,官方文档更倾向于使用实用的方式,而不是实际实现只读行为。我想这成为 Python之禅的又一个例子:

简洁胜于复杂。

实用性胜过纯粹。


4
在Python中,常量只是一个名字全部大写的变量,单词之间用下划线字符分隔,例如DAYS_IN_WEEK=7。这个值是可变的,也就是说你可以改变它。但是,既然名称规则告诉你它是一个常量,为什么要改变它呢?毕竟这是你自己的程序!这是Python一贯的做法。出于同样的原因,Python没有private关键字。在名称前加下划线,你就知道它是私有的。代码可以打破规则...就像程序员可以删除private关键字一样。Python本来可以添加const关键字...但是如果程序员想要删除关键字并更改常量,他们可以这样做,但为什么要这样做呢?如果你想打破规则,你可以改变规则。但是,如果名称清楚地表明了意图,为什么要打破规则呢?也许有一些单元测试需要对值进行更改才有意义?即使在现实世界中一周的天数不能更改,在某些情况下,你可能想看看8天的一周会发生什么。如果语言阻止你打破规则,那么如果只有这一个特殊情况需要打破规则,你就必须停止将其声明为常量,即使它在应用程序中仍然是常量,只有这一个测试用例会看到如果更改了它会发生什么。全部大写的名称告诉你它旨在成为常量。这才是最重要的。而不是语言对你可以随意更改的代码强加约束。这就是Python的哲学。

2
你可以使用 collections.namedtupleitertools 来实现这个功能:
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
您可以使用StringVar或IntVar等,您的常量是。
val = 'Stackoverflow'
const_val = StringVar(val)
const.trace('w', reverse)

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

2
所有给出的答案基本上可以归为两种类型:
  1. 创建某种对象,为其创建属性,一旦定义就不能更改。
  2. 使用一种约定(例如将常量写成全大写或者在Python 3.8中使用final限定符来表示您打算将一个或多个名称设置为常量)。
它们可以概括为“你无法使用Python实现你所要求的功能”。
然而,实际上有一种方法可以创建具有真正常量的模块。这样做的代码相当复杂,我只会概述需要做什么,因为它已经在开源许可下提供。
  1. 使用导入钩子启用创建自定义模块的功能。我使用的多功能代码可以在此处找到
  2. 创建一个特殊的dict,允许添加符合您选择的模式的项目(例如所有大写字母的名称)仅一次,并防止这些名称的值被更改。为此,您需要定义自己的方法,例如__setitem____delitem__等。这样的字典的代码(例如在此文件中找到的代码,超过250行)大约有100行。
  3. 普通Python模块的dict无法修改。因此,在创建模块时,您需要首先执行特殊字典中的代码,然后使用其内容来更新模块的字典。
  4. 为了防止从模块外部修改常量的值(即猴子补丁),可以将模块的__class__替换为自定义的__setattr____delattr__方法重新定义的类。
关于这个示例的文档可以在这里找到。它可能需要更新以反映给出此问题的答案数量。

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