如何在 Python 中创建常量?
- 2024-12-25 08:51:00
- admin 原创
- 147
问题描述:
如何在 Python 中声明常量?
在 Java 中,我们这样做:
public static final String CONST_NAME = "Name";
解决方案 1:
您不能在 Python 中将变量或值声明为常量。
为了向程序员表明一个变量是常量,通常用大写字母书写:
CONST_NAME = "Name"
要在常量更改时引发异常,请参阅Alex Martelli 撰写的《Python 中的常量》。请注意,这在实践中并不常用。
从 Python 3.8 开始,有一个typing.Final
变量注释会告诉静态类型检查器(如 mypy)你的变量不应重新分配。这是最接近 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
解决方案 2:
与其他语言不同,这里没有const
关键字,但是可以创建一个具有“getter 函数”来读取数据,但没有“setter 函数”来重写数据的属性。这实质上保护了标识符不被更改。
以下是使用类属性的另一种实现:
请注意,对于对常量感到疑惑的读者来说,该代码并不容易理解。请参阅下面的解释。
def constant(f):
def fset(self, value):
raise TypeError
def fget(self):
return f()
return property(fget, fset)
class _Const(object):
@constant
def FOO():
return 0xBAADFACE
@constant
def BAR():
return 0xDEADBEEF
CONST = _Const()
print(hex(CONST.FOO)) # -> '0xbaadfaceL'
CONST.FOO = 0
##Traceback (most recent call last):
## File "example1.py", line 22, in <module>
## CONST.FOO = 0
## File "example1.py", line 5, in fset
## raise TypeError
##TypeError
代码解释:
定义一个接受表达式的函数
constant
,并使用它来构造一个“getter”——一个仅返回表达式值的函数。setter 函数会引发 TypeError,因此它是只读的
使用
constant
我们刚刚创建的函数作为装饰来快速定义只读属性。
还有一种更老式的方式:
(代码比较复杂,下面有更多解释)
class _Const(object):
def FOO():
def fset(self, value):
raise TypeError
def fget(self):
return 0xBAADFACE
return property(**locals())
FOO = FOO() # Define property.
CONST = _Const()
print(hex(CONST.FOO)) # -> '0xbaadfaceL'
CONST.FOO = 0
##Traceback (most recent call last):
## File "example2.py", line 16, in <module>
## CONST.FOO = 0
## File "example2.py", line 6, in fset
## raise TypeError
##TypeError
为了定义标识符 FOO,首先定义两个函数(fset、fget——名称由我选择)。
然后使用内置
property
函数构造一个可以“设置”或“获取”的对象。请注意,该
property
函数的前两个参数名为fset
和fget
。利用我们为自己的 getter 和 setter 选择这些名称的事实,并使用 **(双星号)应用于该范围的所有本地定义来创建关键字字典,以将参数传递给
property
函数
解决方案 3:
__method
在 Python 中,人们使用命名约定(例如,对于私有方法,使用),而不是语言强制执行某些操作,_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
解决方案 4:
我最近发现了一个非常简洁的更新,它会自动引发有意义的错误消息并阻止通过以下方式访问__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
对象上。有一个上级类,类名也看起来有点糟糕,但我认为它总体上相当简洁。
解决方案 5:
Python 没有常量。
也许最简单的替代方法是为其定义一个函数:
def MY_CONSTANT():
return 42
MY_CONSTANT()
现在具有常量的所有功能(加上一些烦人的括号)。
解决方案 6:
属性是创建常量的一种方式。您可以通过声明 getter 属性,但忽略 setter 来实现。例如:
class MyFinalProperty(object):
@property
def name(self):
return "John"
您可以查看我写的一篇文章来了解更多使用 Python 属性的方法。
解决方案 7:
除了前两个答案(只需使用名称为大写的变量,或使用属性使值成为只读)之外,我想提一下,可以使用元类来实现命名常量。我在GitHub上提供了一个使用元类的非常简单的解决方案,如果您希望值更清楚地说明其类型/名称,这可能会有所帮助:
>>> from named_constants import Constants
>>> class Colors(Constants):
... black = 0
... red = 1
... white = 15
...
>>> c = Colors.black
>>> c == 0
True
>>> c
Colors.black
>>> c.name()
'black'
>>> Colors(0) is c
True
这是稍微高级一点的 Python,但仍然非常易于使用和方便。(该模块还有一些其他功能,包括常量为只读,请参阅其 README。)
各种存储库中都有类似的解决方案,但据我所知,它们要么缺少我期望常量具备的基本特性之一(例如是常量,或者是任意类型),要么添加了深奥的特性,使它们不那么普遍适用。但 YMMV,我将非常感激您的反馈。:-)
解决方案 8:
编辑:添加了 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
或者,如果你使用的是 Python 3:
class Const(object, metaclass=MetaConst):
def __getattr__(self, name):
return self[name]
def __setattr__(self, name, value):
raise TypeError
这应该可以防止实例 props 被更改。要使用它,请继承:
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 的另一个示例。
解决方案 9:
PEP 591有“final”限定词。执行由类型检查器决定。
因此你可以这样做:
MY_CONSTANT: Final = 12407
注意: Final
该关键字仅适用于 Python 3.8 版本
解决方案 10:
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。
解决方案 11:
我使用冻结数据类声明常量值,如下所示:
from dataclasses import dataclass
@dataclass(frozen=True)
class _Const:
SOME_STRING = 'some_string'
SOME_INT = 5
Const = _Const()
# In another file import Const and try
print(Const.SOME_STRING) # ITS OK!
Const.SOME_INT = 6 # dataclasses.FrozenInstanceError: cannot assign to field 'SOME_INT'
解决方案 12:
您可以使用命名元组作为一种解决方法来有效地创建一个常量,其工作方式与 Java 中的静态最终变量(Java“常量”)相同。作为解决方法,它是一种优雅的方法。(一种更优雅的方法是简单地改进 Python 语言 --- 哪种语言允许您重新定义math.pi
? -- 但我离题了。)
(当我写这篇文章时,我意识到该问题的另一个答案提到了 namedtuple,但我会在这里继续,因为我将展示一种与您在 Java 中期望的更接近的语法,因为不需要像 namedtuple 强迫您那样创建命名类型。)
按照你的例子,你会记得在 Java 中我们必须在某个类中定义常量;因为你没有提到类名,所以我们就叫它吧Foo
。这是 Java 类:
public class Foo {
public static final String CONST_NAME = "Name";
}
这是等效的 Python。
from collections import namedtuple
Foo = namedtuple('_Foo', 'CONST_NAME')('Name')
我想在这里补充的关键点是,您不需要单独的Foo
类型(“匿名命名元组”就很好,即使这听起来像是矛盾的说法),所以我们命名我们的命名元组_Foo
,以便希望它不会逃逸到导入模块。
这里的第二点是,我们立即创建nametuple 的一个实例,并调用它Foo
;没有必要在单独的步骤中执行此操作(除非您愿意)。现在您可以执行在 Java 中可以执行的操作:
>>> Foo.CONST_NAME
'Name'
但你不能分配给它:
>>> Foo.CONST_NAME = 'bar'
…
AttributeError: can't set attribute
致谢:我以为我发明了命名元组方法,但后来我看到其他人给出了类似(尽管不太简洁)的答案。然后我还注意到Python 中的“命名元组”是什么?,其中指出sys.version_info
现在是一个命名元组,所以也许 Python 标准库早就提出了这个想法。
请注意,不幸的是(这仍然是 Python),您可以完全删除整个Foo
作业:
>>> Foo = 'bar'
(捂脸)
但至少我们阻止了Foo.CONST_NAME
值被改变,这比什么都没有要好。祝你好运。
解决方案 13:
以下是“常量”类的实现,它创建具有只读(常量)属性的实例。例如,可以使用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 __的想法。
或者对于具有更多功能的实现,请参阅GitHub 上的@Hans_meine 的
named_constants
解决方案 14:
从技术上讲,元组可以算作常量,因为如果您尝试更改元组的某个值,它将引发错误。如果您要声明一个只有一个值的元组,请在其唯一值后放置一个逗号,如下所示:
my_tuple = (0 """Or any other value""",)
要检查此变量的值,请使用类似如下的方法:
if my_tuple[0] == 0:
#Code goes here
如果您尝试更改该值,将会出现错误。
解决方案 15:
我们可以创建一个描述符对象。
class Constant:
def __init__(self,value=None):
self.value = value
def __get__(self,instance,owner):
return self.value
def __set__(self,instance,value):
raise ValueError("You can't change a constant")
1)如果我们想在实例级别使用常量,那么:
class A:
NULL = Constant()
NUM = Constant(0xFF)
class B:
NAME = Constant('bar')
LISTA = Constant([0,1,'INFINITY'])
>>> obj=A()
>>> print(obj.NUM) #=> 255
>>> obj.NUM =100
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: You can't change a constant
2)如果我们只想在类级别创建常量,我们可以使用元类作为常量(我们的描述符对象)的容器;所有后代类都将继承我们的常量(我们的描述符对象),而不存在任何可以修改的风险。
# metaclass of my class Foo
class FooMeta(type): pass
# class Foo
class Foo(metaclass=FooMeta): pass
# I create constants in my metaclass
FooMeta.NUM = Constant(0xff)
FooMeta.NAME = Constant('FOO')
>>> Foo.NUM #=> 255
>>> Foo.NAME #=> 'FOO'
>>> Foo.NUM = 0 #=> ValueError: You can't change a constant
如果我创建 Foo 的子类,该类将继承该常量,但无法修改它们
class Bar(Foo): pass
>>> Bar.NUM #=> 255
>>> Bar.NUM = 0 #=> ValueError: You can't change a constant
解决方案 16:
这是我创建的成语集合,旨在改进一些现有的答案。
我知道使用常量不是 Pythonic 的,你不应该在家里这样做!
然而,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'"
解决方案 17:
我将创建一个类来覆盖__setattr__
基对象类的方法并用它包装我的常量,请注意我使用的是 python 2.7:
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)
要包装字符串:
>>> constObj = const("Try to change me")
>>> constObj.value
'Try to change me'
>>> constObj.value = "Changed"
Traceback (most recent call last):
...
ValueError: Trying to change a constant value
>>> constObj2 = const(" or not")
>>> mutableObj = constObj.value + constObj2.value
>>> mutableObj #just a string
'Try to change me or not'
这很简单,但如果你想像使用非常量对象一样使用常量(而不使用 constObj.value),那么就会更加复杂。这可能会导致问题,因此最好保持显示并.value
知道你正在使用常量进行操作(尽管这可能不是最“pythonic”的方式)。
解决方案 18:
不幸的是,Python 还没有常量,这很可惜。ES6 已经为 JavaScript 添加了支持常量(https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Statements/const),因为它在任何编程语言中都非常有用。正如 Python 社区中的其他答案所回答的那样,使用约定 - 用户大写变量作为常量,但它不能防止代码中出现任意错误。如果您愿意,您可能会发现下一个单文件解决方案很有用(请参阅文档字符串如何使用它)。
文件 constants.py
import collections
__all__ = ('const', )
class Constant(object):
"""
Implementation strict constants in Python 3.
A constant can be set up, but can not be changed or deleted.
Value of constant may any immutable type, as well as list or set.
Besides if value of a constant is list or set, it will be converted in an immutable type as next:
list -> tuple
set -> frozenset
Dict as value of a constant has no support.
>>> const = Constant()
>>> del const.temp
Traceback (most recent call last):
NameError: name 'temp' is not defined
>>> const.temp = 1
>>> const.temp = 88
Traceback (most recent call last):
...
TypeError: Constanst can not be changed
>>> del const.temp
Traceback (most recent call last):
...
TypeError: Constanst can not be deleted
>>> const.I = ['a', 1, 1.2]
>>> print(const.I)
('a', 1, 1.2)
>>> const.F = {1.2}
>>> print(const.F)
frozenset([1.2])
>>> const.D = dict()
Traceback (most recent call last):
...
TypeError: dict can not be used as constant
>>> del const.UNDEFINED
Traceback (most recent call last):
...
NameError: name 'UNDEFINED' is not defined
>>> const()
{'I': ('a', 1, 1.2), 'temp': 1, 'F': frozenset([1.2])}
"""
def __setattr__(self, name, value):
"""Declaration a constant with value. If mutable - it will be converted to immutable, if possible.
If the constant already exists, then made prevent againt change it."""
if name in self.__dict__:
raise TypeError('Constanst can not be changed')
if not isinstance(value, collections.Hashable):
if isinstance(value, list):
value = tuple(value)
elif isinstance(value, set):
value = frozenset(value)
elif isinstance(value, dict):
raise TypeError('dict can not be used as constant')
else:
raise ValueError('Muttable or custom type is not supported')
self.__dict__[name] = value
def __delattr__(self, name):
"""Deny against deleting a declared constant."""
if name in self.__dict__:
raise TypeError('Constanst can not be deleted')
raise NameError("name '%s' is not defined" % name)
def __call__(self):
"""Return all constans."""
return self.__dict__
const = Constant()
if __name__ == '__main__':
import doctest
doctest.testmod()
如果这还不够,请参阅完整的测试用例。
import decimal
import uuid
import datetime
import unittest
from ..constants import Constant
class TestConstant(unittest.TestCase):
"""
Test for implementation constants in the Python
"""
def setUp(self):
self.const = Constant()
def tearDown(self):
del self.const
def test_create_constant_with_different_variants_of_name(self):
self.const.CONSTANT = 1
self.assertEqual(self.const.CONSTANT, 1)
self.const.Constant = 2
self.assertEqual(self.const.Constant, 2)
self.const.ConStAnT = 3
self.assertEqual(self.const.ConStAnT, 3)
self.const.constant = 4
self.assertEqual(self.const.constant, 4)
self.const.co_ns_ta_nt = 5
self.assertEqual(self.const.co_ns_ta_nt, 5)
self.const.constant1111 = 6
self.assertEqual(self.const.constant1111, 6)
def test_create_and_change_integer_constant(self):
self.const.INT = 1234
self.assertEqual(self.const.INT, 1234)
with self.assertRaisesRegexp(TypeError, 'Constanst can not be changed'):
self.const.INT = .211
def test_create_and_change_float_constant(self):
self.const.FLOAT = .1234
self.assertEqual(self.const.FLOAT, .1234)
with self.assertRaisesRegexp(TypeError, 'Constanst can not be changed'):
self.const.FLOAT = .211
def test_create_and_change_list_constant_but_saved_as_tuple(self):
self.const.LIST = [1, .2, None, True, datetime.date.today(), [], {}]
self.assertEqual(self.const.LIST, (1, .2, None, True, datetime.date.today(), [], {}))
self.assertTrue(isinstance(self.const.LIST, tuple))
with self.assertRaisesRegexp(TypeError, 'Constanst can not be changed'):
self.const.LIST = .211
def test_create_and_change_none_constant(self):
self.const.NONE = None
self.assertEqual(self.const.NONE, None)
with self.assertRaisesRegexp(TypeError, 'Constanst can not be changed'):
self.const.NONE = .211
def test_create_and_change_boolean_constant(self):
self.const.BOOLEAN = True
self.assertEqual(self.const.BOOLEAN, True)
with self.assertRaisesRegexp(TypeError, 'Constanst can not be changed'):
self.const.BOOLEAN = False
def test_create_and_change_string_constant(self):
self.const.STRING = "Text"
self.assertEqual(self.const.STRING, "Text")
with self.assertRaisesRegexp(TypeError, 'Constanst can not be changed'):
self.const.STRING += '...'
with self.assertRaisesRegexp(TypeError, 'Constanst can not be changed'):
self.const.STRING = 'TEst1'
def test_create_dict_constant(self):
with self.assertRaisesRegexp(TypeError, 'dict can not be used as constant'):
self.const.DICT = {}
def test_create_and_change_tuple_constant(self):
self.const.TUPLE = (1, .2, None, True, datetime.date.today(), [], {})
self.assertEqual(self.const.TUPLE, (1, .2, None, True, datetime.date.today(), [], {}))
with self.assertRaisesRegexp(TypeError, 'Constanst can not be changed'):
self.const.TUPLE = 'TEst1'
def test_create_and_change_set_constant(self):
self.const.SET = {1, .2, None, True, datetime.date.today()}
self.assertEqual(self.const.SET, {1, .2, None, True, datetime.date.today()})
self.assertTrue(isinstance(self.const.SET, frozenset))
with self.assertRaisesRegexp(TypeError, 'Constanst can not be changed'):
self.const.SET = 3212
def test_create_and_change_frozenset_constant(self):
self.const.FROZENSET = frozenset({1, .2, None, True, datetime.date.today()})
self.assertEqual(self.const.FROZENSET, frozenset({1, .2, None, True, datetime.date.today()}))
with self.assertRaisesRegexp(TypeError, 'Constanst can not be changed'):
self.const.FROZENSET = True
def test_create_and_change_date_constant(self):
self.const.DATE = datetime.date(1111, 11, 11)
self.assertEqual(self.const.DATE, datetime.date(1111, 11, 11))
with self.assertRaisesRegexp(TypeError, 'Constanst can not be changed'):
self.const.DATE = True
def test_create_and_change_datetime_constant(self):
self.const.DATETIME = datetime.datetime(2000, 10, 10, 10, 10)
self.assertEqual(self.const.DATETIME, datetime.datetime(2000, 10, 10, 10, 10))
with self.assertRaisesRegexp(TypeError, 'Constanst can not be changed'):
self.const.DATETIME = None
def test_create_and_change_decimal_constant(self):
self.const.DECIMAL = decimal.Decimal(13123.12312312321)
self.assertEqual(self.const.DECIMAL, decimal.Decimal(13123.12312312321))
with self.assertRaisesRegexp(TypeError, 'Constanst can not be changed'):
self.const.DECIMAL = None
def test_create_and_change_timedelta_constant(self):
self.const.TIMEDELTA = datetime.timedelta(days=45)
self.assertEqual(self.const.TIMEDELTA, datetime.timedelta(days=45))
with self.assertRaisesRegexp(TypeError, 'Constanst can not be changed'):
self.const.TIMEDELTA = 1
def test_create_and_change_uuid_constant(self):
value = uuid.uuid4()
self.const.UUID = value
self.assertEqual(self.const.UUID, value)
with self.assertRaisesRegexp(TypeError, 'Constanst can not be changed'):
self.const.UUID = []
def test_try_delete_defined_const(self):
self.const.VERSION = '0.0.1'
with self.assertRaisesRegexp(TypeError, 'Constanst can not be deleted'):
del self.const.VERSION
def test_try_delete_undefined_const(self):
with self.assertRaisesRegexp(NameError, "name 'UNDEFINED' is not defined"):
del self.const.UNDEFINED
def test_get_all_defined_constants(self):
self.assertDictEqual(self.const(), {})
self.const.A = 1
self.assertDictEqual(self.const(), {'A': 1})
self.const.B = "Text"
self.assertDictEqual(self.const(), {'A': 1, 'B': "Text"})
优点:1. 可以访问整个项目的所有常量 2. 对常量的值进行严格控制
缺点:1.不支持自定义类型和“dict”类型
笔记:
已使用 Python3.4 和 Python3.5 进行测试(我使用 'tox')
测试环境:
。
$ uname -a
Linux wlysenko-Aspire 3.13.0-37-generic #64-Ubuntu SMP Mon Sep 22 21:28:38 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux
解决方案 19:
有一种更简洁的方法,即使用 namedtuple 来实现这一点:
from collections import namedtuple
def make_consts(name, **kwargs):
return namedtuple(name, kwargs.keys())(**kwargs)
使用示例
CONSTS = make_consts("baz1",
foo=1,
bar=2)
通过这种方法,您可以为常量设置命名空间。
解决方案 20:
声明“常量”的 Python 方式基本上是一个模块级变量:
RED = 1
GREEN = 2
BLUE = 3
然后编写类或函数。由于常量几乎总是整数,并且它们在 Python 中也是不可变的,因此您很少有机会改变它。
当然,除非你明确设置RED = 2
。
解决方案 21:
如果您想要常量而不关心它们的值,这里有一个技巧:
只需定义空类。
例如:
class RED:
pass
class BLUE:
pass
解决方案 22:
没有完美的方法可以做到这一点。据我所知,大多数程序员只会将标识符大写,因此 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
这对于您在此处找到的所有其他解决方案都是一样的 - 即使是创建类并重新定义设置属性方法的聪明方法 - 也总会有解决方法。这就是 Python 的本质。
我的建议是避免所有麻烦,只需将标识符大写即可。这实际上不是一个合适的常量,但话又说回来,什么也不会。
解决方案 23:
我正在尝试不同的方法在 Python 中创建一个实数常量,也许我找到了一个很好的解决方案。
例子:
创建常量容器
>>> DAYS = Constants(
... MON=0,
... TUE=1,
... WED=2,
... THU=3,
... FRI=4,
... SAT=5,
... SUN=6
... )
从容器中获取价值
>>> DAYS.MON
0
>>> DAYS['MON']
0
用纯 Python 数据结构表示
>>> list(DAYS)
['WED', 'SUN', 'FRI', 'THU', 'MON', 'TUE', 'SAT']
>>> dict(DAYS)
{'WED': 2, 'SUN': 6, 'FRI': 4, 'THU': 3, 'MON': 0, 'TUE': 1, 'SAT': 5}
所有常量都是不可变的
>>> DAYS.MON = 7
...
AttributeError: Immutable attribute
>>> del DAYS.MON
...
AttributeError: Immutable attribute
仅对常量进行自动完成
>>> dir(DAYS)
['FRI', 'MON', 'SAT', 'SUN', 'THU', 'TUE', 'WED']
排序方式list.sort
>>> DAYS.sort(key=lambda (k, v): v, reverse=True)
>>> list(DAYS)
['SUN', 'SAT', 'FRI', 'THU', 'WED', 'TUE', 'MON']
兼容python2
和python3
常量的简单容器
from collections import OrderedDict
from copy import deepcopy
class Constants(object):
"""Container of constant"""
__slots__ = ('__dict__')
def __init__(self, **kwargs):
if list(filter(lambda x: not x.isupper(), kwargs)):
raise AttributeError('Constant name should be uppercase.')
super(Constants, self).__setattr__(
'__dict__',
OrderedDict(map(lambda x: (x[0], deepcopy(x[1])), kwargs.items()))
)
def sort(self, key=None, reverse=False):
super(Constants, self).__setattr__(
'__dict__',
OrderedDict(sorted(self.__dict__.items(), key=key, reverse=reverse))
)
def __getitem__(self, name):
return self.__dict__[name]
def __len__(self):
return len(self.__dict__)
def __iter__(self):
for name in self.__dict__:
yield name
def keys(self):
return list(self)
def __str__(self):
return str(list(self))
def __repr__(self):
return '<%s: %s>' % (self.__class__.__name__, str(self.__dict__))
def __dir__(self):
return list(self)
def __setattr__(self, name, value):
raise AttributeError("Immutable attribute")
def __delattr__(*_):
raise AttributeError("Immutable attribute")
解决方案 24:
Python 字典是可变的,因此它们似乎不是声明常量的好方法:
>>> constants = {"foo":1, "bar":2}
>>> print constants
{'foo': 1, 'bar': 2}
>>> constants["bar"] = 3
>>> print constants
{'foo': 1, 'bar': 3}
解决方案 25:
在 Python 中,常量只是一个变量,其名称全部用大写字母表示,每个单词之间用下划线分隔,
例如
一周中有几天 = 7
该值是可变的,也就是说你可以改变它。但是考虑到名称的规则告诉你它是一个常量,你为什么要这么做呢?我的意思是,毕竟这是你的程序!
这是整个 Python 中采用的方法。出于同样的原因,没有private
关键字。在名称前面加上下划线,您就知道它是私有的。代码可以打破规则……就像程序员无论如何都可以删除 private 关键字一样。
Python 本来可以添加一个const
关键字……但是程序员可以删除关键字,然后根据需要更改常量,但为什么要这样做呢?如果你想打破规则,你无论如何都可以改变规则。但是如果名称清楚地表明了意图,为什么要打破规则呢?
也许有一些单元测试可以对值进行更改?看看 8 天一周会发生什么,即使在现实世界中一周的天数是无法改变的。如果语言阻止你做出例外,如果只有这种情况,你需要打破规则……那么你就必须停止将其声明为常量,即使它在应用程序中仍然是一个常量,并且只有这一个测试用例可以查看如果更改它会发生什么。
全部大写的名称告诉您它旨在成为常量。这才是最重要的。这不是一种强制限制代码的语言,您无论如何都有权更改它。
这就是python的哲学。
解决方案 26:
(本段原本是要对这里和那里提到的那些答案进行评论namedtuple
,但它太长了,无法容纳在评论中,因此,就在这里。)
上面提到的namedtuple方法无疑是创新的。不过,为了完整起见,在其官方文档的NamedTuple部分末尾,它写道:
枚举常量可以用命名元组来实现,但使用简单的类声明更简单、更高效:
class Status: open, pending, closed = range(3)
换句话说,官方文档更喜欢使用一种实用的方式,而不是真正实现只读行为。我想这又成为了Python 之禅的另一个例子:
简单比复杂更好。
实用性胜过纯粹性。
解决方案 27:
也许 pconst 库会帮助你(github)。
$ pip install pconst
from pconst import const
const.APPLE_PRICE = 100
const.APPLE_PRICE = 200
[Out] Constant value of "APPLE_PRICE" is not editable.
解决方案 28:
您可以使用 StringVar 或 IntVar 等,您的常量是const_val
val = 'Stackoverflow'
const_val = StringVar(val)
const.trace('w', reverse)
def reverse(*args):
const_val.set(val)
解决方案 29:
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
解决方案 30:
CONST_
在 Python 中,常量不存在,但你可以通过在变量名开头添加并在注释中声明它是一个常量来表明一个变量是一个常量并且不能更改:
myVariable = 0
CONST_daysInWeek = 7 # This is a constant - do not change its value.
CONSTANT_daysInMonth = 30 # This is also a constant - do not change this value.
或者,您可以创建一个像常量一样的函数:
def CONST_daysInWeek():
return 7;