如何在Python中声明常量?
在Java中,我们做:
public static final String CONST_NAME = "Name";
如何在Python中声明常量?
在Java中,我们做:
public static final String CONST_NAME = "Name";
当前回答
嗯. .尽管这是过时的,让我在这里补充我的2分:-)
class ConstDict(dict):
def __init__(self, *args, **kwargs):
super(ConstDict, self).__init__(*args, **kwargs)
def __setitem__(self, key, value):
if key in self:
raise ValueError("Value %s already exists" % (key))
super(ConstDict, self).__setitem__(key, value)
您可以防止在那里发生任何更新,而不是破坏ValueError。这样做的一个好处是,您可以在程序中动态地添加常数,但一旦设置了常数就不能更改。你也可以在设置常量之前添加任何规则(比如key必须是字符串或小写字符串或大写字符串等)
然而,我不认为在Python中设置常量有任何重要性。没有优化可以像在C中那样发生,因此它是不需要的,我猜。
其他回答
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完全弃用。
在我的例子中,我需要不可变字节数组来实现包含许多文字数字的加密库,我想确保这些数字是常量。
这个答案是有效的,但是尝试重赋bytearray元素不会引发错误。
def const(func):
'''implement const decorator'''
def fset(self, val):
'''attempting to set a const raises `ConstError`'''
class ConstError(TypeError):
'''special exception for const reassignment'''
pass
raise ConstError
def fget(self):
'''get a const'''
return func()
return property(fget, fset)
class Consts(object):
'''contain all constants'''
@const
def C1():
'''reassignment to C1 fails silently'''
return bytearray.fromhex('deadbeef')
@const
def pi():
'''is immutable'''
return 3.141592653589793
常量是不可变的,但是常量bytearray赋值默默失败:
>>> c = Consts()
>>> c.pi = 6.283185307179586 # (https://en.wikipedia.org/wiki/Tau_(2%CF%80))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "consts.py", line 9, in fset
raise ConstError
__main__.ConstError
>>> c.C1[0] = 0
>>> c.C1[0]
222
>>> c.C1
bytearray(b'\xde\xad\xbe\xef')
一种更强大、更简单,甚至可能更“python化”的方法涉及使用memoryview对象(<= python-2.6中的缓冲区对象)。
import sys
PY_VER = sys.version.split()[0].split('.')
if int(PY_VER[0]) == 2:
if int(PY_VER[1]) < 6:
raise NotImplementedError
elif int(PY_VER[1]) == 6:
memoryview = buffer
class ConstArray(object):
'''represent a constant bytearray'''
def __init__(self, init):
'''
create a hidden bytearray and expose a memoryview of that bytearray for
read-only use
'''
if int(PY_VER[1]) == 6:
self.__array = bytearray(init.decode('hex'))
else:
self.__array = bytearray.fromhex(init)
self.array = memoryview(self.__array)
def __str__(self):
return str(self.__array)
def __getitem__(self, *args, **kwargs):
return self.array.__getitem__(*args, **kwargs)
ConstArray项赋值是一个TypeError:
>>> C1 = ConstArray('deadbeef')
>>> C1[0] = 0
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'ConstArray' object does not support item assignment
>>> C1[0]
222
在Python中创建常量的更好方法是从 优秀的attrs库,这很有帮助 Python程序员创建类时不使用样板文件。的 Short-con package也有同样的功能 常量,提供一个方便的包装器 attr.make_class。[声明:我是short-con的作者。]
值可以通过dict或kwargs显式声明 例子也有同样的作用。常量()函数支持的所有特性 库和cons()是简单使用kwarg的辅助程序。
from short_con import constants, cons
Pieces = constants('Pieces', dict(king = 0, queen = 9, rook = 5, bishop = 3, knight = 3, pawn = 1))
Pieces = cons('Pieces', king = 0, queen = 9, rook = 5, bishop = 3, knight = 3, pawn = 1)
的值相同(或可以从其导出)的情况 属性名,使用更加紧凑。只提供名称作为 空格分隔的字符串、列表或元组。
NAMES = 'KING QUEEN ROOK BISHOP KNIGHT PAWN'
xs = NAMES.split()
Pieces = constants('Pieces', NAMES) # All of these do the same thing.
Pieces = constants('Pieces', xs)
Pieces = constants('Pieces', tuple(xs))
基于名称的用法支持一些风格约定:大写或 小写的属性名,以及枚举样式的值。
方法创建的常量不同,底层值可以直接访问 内置枚举库:
Pieces.QUEEN # short-con usage
Pieces.QUEEN.value # enum library usage
对象直接可迭代,可转换为其他集合:
for name, value in Pieces:
print(name, value)
d = dict(Pieces)
tups = list(Pieces)
没有完美的方法可以做到这一点。据我所知,大多数程序员只是将标识符大写,所以PI = 3.142可以很容易地理解为一个常数。
另一方面,如果你想要一个像常数一样的东西,我不确定你能找到它。无论你做什么,总会有一些方法来编辑“常数”,所以它不是一个真正的常数。这里有一个非常简单的例子:
def define(name, value):
if (name + str(id(name))) not in globals():
globals()[name + str(id(name))] = value
def constant(name):
return globals()[name + str(id(name))]
define("PI",3.142)
print(constant("PI"))
这看起来像是一个php风格的常量。
在现实中,所有需要改变值的人是这样的:
globals()["PI"+str(id("PI"))] = 3.1415
这对于你在这里找到的所有其他解决方案都是一样的——即使是那些创建类并重新定义set属性方法的聪明的解决方案——总有一种方法可以绕过它们。Python就是这样的。
我的建议是避免所有的麻烦,将标识符大写。它不是一个合适的常数,但也没有什么合适的常数。
我知道这是一个老问题,但由于新的解决方案仍在添加,我想使可能的解决方案列表更加完整。你可以通过从类中继承属性来实现实例中的常量,如下所示:
class ConstantError(Exception):
pass # maybe give nice error message
class AllowConstants:
_constants = None
_class_constants = None
def __init__(self):
self._constants = {}
if self._class_constants is not None:
self._constants.update(self._class_constants)
def constant(self, name, value):
assert isinstance(name, str)
assert self._constants is not None, "AllowConstants was not initialized"
if name in self._constants or name in self.__dict__:
raise ConstantError(name)
self._constants[name] = value
def __getattr__(self, attr):
if attr in self._constants:
return self._constants[attr]
raise AttributeError(attr)
def __setattr__(self, attr, val):
if self._constants is None:
# not finished initialization
self.__dict__[attr] = val
else:
if attr in self._constants:
raise ConstantError(attr)
else:
self.__dict__[attr] = val
def __dir__(self):
return super().__dir__() + list(self._constants.keys())
子类化this时,你创建的常量将受到保护:
class Example(AllowConstants):
def __init__(self, a, b):
super().__init__()
self.constant("b", b)
self.a = a
def try_a(self, value):
self.a = value
def try_b(self, value):
self.b = value
def __str__(self):
return str({"a": self.a, "b": self.b})
def __repr__(self):
return self.__str__()
example = Example(1, 2)
print(example) # {'a': 1, 'b': 2}
example.try_a(5)
print(example) # {'a': 5, 'b': 2}
example.try_b(6) # ConstantError: b
example.a = 7
print(example) # {'a': 7, 'b': 2}
example.b = 8 # ConstantError: b
print(hasattr(example, "b")) # True
# To show that constants really do immediately become constant:
class AnotherExample(AllowConstants):
def __init__(self):
super().__init__()
self.constant("a", 2)
print(self.a)
self.a=3
AnotherExample() # 2 ConstantError: a
# finally, for class constants:
class YetAnotherExample(Example):
_class_constants = {
'BLA': 3
}
def __init__(self, a, b):
super().__init__(a,b)
def try_BLA(self, value):
self.BLA = value
ex3 = YetAnotherExample(10, 20)
ex3.BLA # 3
ex3.try_BLA(10) # ConstantError: BLA
ex3.BLA = 4 # ConstantError: BLA
常量是局部的(从AllowConstants继承的类的每个实例都有自己的常量),只要它们没有被重新赋值,就像普通的属性一样,并且编写从这个继承的类允许或多或少与支持常量的语言相同的风格。
此外,如果您想通过直接访问实例来防止任何人更改值。_constants,您可以使用其他答案中建议的许多不允许这样做的容器之一。最后,如果你真的觉得有必要,你可以阻止人们设置所有的实例。通过AllowConstants的更多属性访问,将_constants赋给一个新字典。(当然,这些都不是非常python化的,但这不是重点)。
编辑(因为使python非python化是一个有趣的游戏):为了使继承更容易一点,你可以修改AllowConstants如下:
class AllowConstants:
_constants = None
_class_constants = None
def __init__(self):
self._constants = {}
self._update_class_constants()
def __init_subclass__(cls):
"""
Without this, it is necessary to set _class_constants in any subclass of any class that has class constants
"""
if cls._class_constants is not None:
#prevent trouble where _class_constants is not overwritten
possible_cases = cls.__mro__[1:-1] #0 will have cls and -1 will have object
for case in possible_cases:
if cls._class_constants is case._class_constants:
cls._class_constants = None
break
def _update_class_constants(self):
"""
Help with the inheritance of class constants
"""
for superclass in self.__class__.__mro__:
if hasattr(superclass, "_class_constants"):
sccc = superclass._class_constants
if sccc is not None:
for key in sccc:
if key in self._constants:
raise ConstantError(key)
self._constants.update(sccc)
def constant(self, name, value):
assert isinstance(name, str)
assert self._constants is not None, "AllowConstants was not initialized"
if name in self._constants or name in self.__dict__:
raise ConstantError(name)
self._constants[name] = value
def __getattr__(self, attr):
if attr in self._constants:
return self._constants[attr]
raise AttributeError(attr)
def __setattr__(self, attr, val):
if self._constants is None:
# not finished initialization
self.__dict__[attr] = val
else:
if attr in self._constants:
raise ConstantError(attr)
else:
self.__dict__[attr] = val
def __dir__(self):
return super().__dir__() + list(self._constants.keys())
这样你就可以:
class Example(AllowConstants):
_class_constants = {
"BLA": 2
}
def __init__(self, a, b):
super().__init__()
self.constant("b", b)
self.a = a
def try_a(self, value):
self.a = value
def try_b(self, value):
self.b = value
def __str__(self):
return str({"a": self.a, "b": self.b})
def __repr__(self):
return self.__str__()
class ChildExample1(Example):
_class_constants = {
"BLI": 88
}
class ChildExample2(Example):
_class_constants = {
"BLA": 44
}
example = ChildExample1(2,3)
print(example.BLA) # 2
example.BLA = 8 # ConstantError BLA
print(example.BLI) # 88
example.BLI = 8 # ConstantError BLI
example = ChildExample2(2,3) # ConstantError BLA