如何在Python中声明常量?

在Java中,我们做:

public static final String CONST_NAME = "Name";

当前回答

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

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

请注意,对于想了解常量的读者来说,代码远非简单。见下面的解释。

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

代码的解释:

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


用另一种更传统的方式:

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

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

要定义标识符FOO,首先定义两个函数(fset, fget -名称由我选择)。 然后使用内置的属性函数构造一个可以“set”或“get”的对象。 注意属性函数的前两个参数名为fset和fget。 利用我们为自己的getter和setter选择这些名称的事实,并使用应用于该作用域的所有本地定义的**(双星号)创建一个关键字字典,将参数传递给属性函数

其他回答

PEP 591有“final”限定符。执行取决于类型检查器。

所以你可以这样做:

MY_CONSTANT: Final = 12407

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

使用namedtuple有一种更干净的方法:

from collections import namedtuple


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

使用的例子

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

使用这种方法,您可以为常数命名空间。

我为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 url: Pypi或github

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


为了让程序员知道变量是常量,通常用大写:

CONST_NAME = "Name"

要在常量发生变化时引发异常,请参阅Alex Martelli的《Python中的常量》。注意,这在实践中并不常用。


从Python 3.8开始,有一个类型。最后一个变量注释,它将告诉静态类型检查器(如myypy)您的变量不应该被重新分配。这是最接近于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式的,你不应该在家里这样做!

然而,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的修改,其中冻结了引用的可变对象。这个实现利用了setattr和getattr函数之间的共享闭包。可变对象的值由函数共享闭包内的变量缓存定义复制和引用。它形成了我所说的可变对象的闭包保护副本。

在使用这种习惯用法时必须小心,因为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的不可变名称空间。它结合了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 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'"