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

0

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

我主要回答了另一个问题,结果发现它与这个问题有关。这个想法是利用审计钩子捕获每行代码的执行,解析代码对象,如果满足某些条件(例如特定前缀已定义一次),则可以抛出错误。

您可能需要支持其他赋值类型(例如导入的内容、函数内部的本地变量、解包等),不使用globals,因为该字典可以轻松修改,实际上调查是否安全,接受此实现将对整个应用程序产生的性能损失,确保此功能在 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'

如果你最终选择这种方式,虽然不应该这样做,除了修复和通用代码之外,你可能想找到一种方法来使某些内容保持不变,例如只有那些具有特殊前缀或带有某些注释的对象。

0

不禁提供了我自己的非常轻量级极简元类实现(可能与先前的元类答案有所不同)。

常量存储在容器类中(无需实例化)。 值可以设置一次,但一旦设置就无法更改(或删除)。

个人目前没有用例,但这是一个有趣的练习。

class MetaConstant(type):
    ''' Metaclass that allows underlying class to store constants at class-level (subclass instance not needed).
        Non-existent attributes of underlying class (constants) can be set initially, but cannot be changed or deleted.
    '''

    def __setattr__(klass, attr, value):
        'If attribute (constant) doesn\'t exist, set value. If attribute exists, raise AttributeError.'
        if hasattr(klass, attr):
            raise AttributeError(f'Can\'t change the value of the constant {klass.__name__}.{attr} to {value}'
                                 f' (the value of {klass.__name__}.{attr} is already set to'
                                 f' {getattr(klass, attr)}).')
        super().__setattr__(attr, value)

    def __delattr__(klass, attr):
        if hasattr(klass, attr):
            raise AttributeError(f'Can\'t delete constant {klass.__name__}.{attr}'
                                 f' (set to {getattr(klass, attr)}).')


class Constants(metaclass=MetaConstant):
    'Container class for constants. No instantiation required.'
    #pass               # uncomment if no constants set upon class creation
    B = 'Six'           # sets Constants.B to 'Six'


Constants.B = 6         # AttributeError
del Constants.B         # AttributeError

Constants.A = 'Five'    # sets Constants.A to 'Five'
Constants.A = 5         # AttributeError
del Constants.A         # AttributeError

欢迎提出改进意见。


-2
您可以通过下一个类来模拟常量变量。以下是使用示例:
# Const
const = Const().add(two=2, three=3)

print 'const.two: ', const.two
print 'const.three: ', const.three

const.add(four=4)

print 'const.four: ', const.four

#const.four = 5 # a error here: four is a constant

const.add(six=6)

print 'const.six: ', const.six

const2 = Const().add(five=5) # creating a new namespace with Const()
print 'const2.five: ', const2.five
#print 'const2.four: ', const2.four # a error here: four does not exist in const2 namespace

const2.add(five=26)

当您想要启动新的常量命名空间时,请调用构造函数。请注意,当Martelli的const类不存在时,该类受到意外修改序列类型常量的保护。

以下是源代码。

from copy import copy

class Const(object):
"A class to create objects with constant fields."

def __init__(self):
    object.__setattr__(self, '_names', [])


def add(self, **nameVals):
    for name, val in nameVals.iteritems():          
        if hasattr(self, name):
            raise ConstError('A field with a name \'%s\' is already exist in Const class.' % name)

        setattr(self, name, copy(val)) # set up getter

        self._names.append(name)

    return self


def __setattr__(self, name, val):
    if name in self._names:
        raise ConstError('You cannot change a value of a stored constant.')

    object.__setattr__(self, name, val)

-2
扩展Raufio的答案,添加一个__repr__来返回值。
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)
    def __repr__(self):
        return ('{0}'.format(self.value))

dt = const(float(0.01))
print dt

那么对象的行为就更像你所期望的了,你可以直接访问它,而不是使用'.value'。


1
不是常量。dt = 5 可以被接受而没有任何投诉。在Raufio的答案中,虽然也可以覆盖它,但结果会导致下一次使用 dt.value 时出现投诉。因此,这是一种较少危险的失败。你已经抵消了他解决方案的好处。 - ToolmakerSteve

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