如何在Python中声明常量?
在Java中,我们做:
public static final String CONST_NAME = "Name";
如何在Python中声明常量?
在Java中,我们做:
public static final String CONST_NAME = "Name";
当前回答
这里是我创建的一些习语的集合,试图改进一些已有的答案。
我知道常量的使用不是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'"
其他回答
下面是一个“Constants”类的实现,它创建具有只读(常量)属性的实例。例如,可以使用Nums。PI来获得一个已初始化为3.14159的值,Nums。PI = 22引发异常。
# ---------- Constants.py ----------
class Constants(object):
"""
Create objects with read-only (constant) attributes.
Example:
Nums = Constants(ONE=1, PI=3.14159, DefaultWidth=100.0)
print 10 + Nums.PI
print '----- Following line is deliberate ValueError -----'
Nums.PI = 22
"""
def __init__(self, *args, **kwargs):
self._d = dict(*args, **kwargs)
def __iter__(self):
return iter(self._d)
def __len__(self):
return len(self._d)
# NOTE: This is only called if self lacks the attribute.
# So it does not interfere with get of 'self._d', etc.
def __getattr__(self, name):
return self._d[name]
# ASSUMES '_..' attribute is OK to set. Need this to initialize 'self._d', etc.
#If use as keys, they won't be constant.
def __setattr__(self, name, value):
if (name[0] == '_'):
super(Constants, self).__setattr__(name, value)
else:
raise ValueError("setattr while locked", self)
if (__name__ == "__main__"):
# Usage example.
Nums = Constants(ONE=1, PI=3.14159, DefaultWidth=100.0)
print 10 + Nums.PI
print '----- Following line is deliberate ValueError -----'
Nums.PI = 22
感谢@MikeGraham的FrozenDict,我将其作为一个起点。更改后,使用语法不再是Nums['ONE'],而是Nums.ONE。
感谢@Raufio的回答,对于覆盖__ setattr __的想法。
或者要了解更多功能的实现,请参阅@Hans_meine的实现 named_constants在GitHub
扩展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 "
所有给出的答案基本上有两种类型:
创建一些你可以实现的对象 创建一旦定义就不能更改的属性。 使用约定(比如常量全部大写,或者对于Python 3.8,使用最后一个限定符来表示一个或多个名称是常量。
他们可以总结为“你不能用Python做你想做的事情”。
然而,实际上有一种方法可以创建具有真正常量的模块。这样做的代码相当复杂,我将只给出需要做什么,因为它在开源许可下已经可用。
使用导入钩子来创建自定义模块。这里可以找到我为此使用的通用代码。 创建一个特殊的字典,允许只添加一次符合您所选模式的项(例如,名称全部大写),并防止此类名称的值被更改。为此,你需要定义自己的方法,如__setitem__, __delitem__等。这种字典的代码(比如在这个文件中找到的,超过250行)大约有100行长。 普通Python模块的dict不能被修改。因此,在创建模块时,您需要首先执行特殊字典中的代码,然后使用其内容更新模块的字典。 为了防止从模块外部修改常量的值(即monkeypatching),您可以用重新定义的__setattr__和__delattr__方法将模块的__class__替换为自定义的__class__。
关于这个示例的文档可以在这里找到。它可能应该更新,以反映这个问题的答案的数量。
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完全弃用。
您可以将一个常量包装在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