如何在Python中声明常量?

在Java中,我们做:

public static final String CONST_NAME = "Name";

当前回答

您可以将一个常量包装在numpy数组中,将其标记为仅写,并始终通过下标0调用它。

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会改变,然而,这将很快抛出TypeError第一次在脚本中调用CONSTANT[0]。

尽管……我想如果你在某个时候把它改成

CONSTANT = [1,2,3]

现在你不会再得到TypeError了。嗯……

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

其他回答

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

或者,如果你使用的是python3:

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

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

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

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

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

你可以通过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

在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

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

我基本上回答了另一个问题,结果和这个问题有关。它的思想是,你可以利用审计钩子来捕捉每一行的执行,解析代码对象,如果它满足某些条件(例如某个前缀并且已经定义过一次),你可以抛出一个错误。

你可能不得不支持其他赋值类型(例如,对于导入的东西,可能对于函数内部的局部变量,解包等),不使用全局变量,因为字典可以很容易地修改,实际上调查这是否安全,接受这个实现将对你的整个应用程序造成的性能损失,确保它在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 = 456
Traceback (most recent call last):
  File "<stdin>", line 16, in hook
Exception: Cannot re-assign variable
>>> 
>>> a
'123'

如果你以这种方式结束,你不应该,除了修复和泛化代码,你可能会想要找到一种方法,只让一些东西不变,例如,只有那些有特殊前缀的对象或只有对象有一些注释。