如何在Python中声明常量?

在Java中,我们做:

public static final String CONST_NAME = "Name";

当前回答

属性是创建常量的一种方法。你可以通过声明一个getter属性来做到这一点,但忽略setter。例如:

class MyFinalProperty(object):

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

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

其他回答

from enum import Enum
class StringConsts(str,Enum):
    ONE='one'
    TWO='two'

print(f'Truth is  {StringConsts.ONE=="one"}') #Truth is True
StringConsts.ONE="one" #Error: Cannot reassign

Enum和str的混合让你不必重新实现setattr(通过Enum),也不必与其他str对象进行比较(通过str)。

这可能会使http://code.activestate.com/recipes/65207-constants-in-python/?in=user-97991完全弃用。

注意:这是一个糟糕的想法和糟糕的实现。此外,它只适用于最后的小例子,一个完整的实现将意味着大量的工作,这是我太懒了。而且,在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'

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

我最近发现了一个非常简洁的更新,它会自动引发有意义的错误消息,并阻止通过__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对象上。有一个上层的类,类名似乎也有点难看,但我认为它总体上是相当简洁的。

元组在技术上属于常量,因为如果您试图更改其中一个值,元组将引发错误。如果你想声明一个只有一个值的元组,那么在它唯一的值后面加一个逗号,就像这样:

my_tuple = (0 """Or any other value""",)

要检查这个变量的值,使用类似这样的方法:

if my_tuple[0] == 0:
    #Code goes here

如果您试图更改此值,将引发一个错误。

这里是我创建的一些习语的集合,试图改进一些已有的答案。

我知道常量的使用不是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'"