如何在Python中声明常量?
在Java中,我们做:
public static final String CONST_NAME = "Name";
如何在Python中声明常量?
在Java中,我们做:
public static final String CONST_NAME = "Name";
当前回答
注意:这是一个糟糕的想法和糟糕的实现。此外,它只适用于最后的小例子,一个完整的实现将意味着大量的工作,这是我太懒了。而且,在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'
如果你以这种方式结束,你不应该,除了修复和泛化代码,你可能会想要找到一种方法,只让一些东西不变,例如,只有那些有特殊前缀的对象或只有对象有一些注释。
其他回答
这并不完全是常数,但从python 3.7开始,你可以使用如下所示的数据类模块:
from dataclasses import dataclass
from typing import Final
@dataclass(frozen=True)
class A():
a1:Final = 3
a = A()
a.a1 = 4
---------------------------------------------------------------------------
FrozenInstanceError Traceback (most recent call last)
<ipython-input-14-5f7f4efc5bf0> in <module>
----> 1 a.a1 = 4
<string> in __setattr__(self, name, value)
FrozenInstanceError: cannot assign to field 'a1'
我正在尝试用不同的方法在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")
您可以在下一个类的帮助下模拟常量变量。用法示例:
# 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)
在其他语言中没有const关键字,但是可以创建一个具有“getter函数”来读取数据,但没有“setter函数”来重写数据的Property。这从本质上保护标识符不被更改。
下面是一个使用class属性的替代实现:
请注意,对于想了解常量的读者来说,代码远非简单。见下面的解释。
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
代码的解释:
定义一个接受表达式的函数常量,并使用它来构造一个“getter”——一个仅返回表达式值的函数。 setter函数引发TypeError,因此它是只读的 使用我们刚刚创建的常量函数作为装饰来快速定义只读属性。
用另一种更传统的方式:
(代码相当棘手,下面有更多解释)
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 -名称由我选择)。 然后使用内置的属性函数构造一个可以“set”或“get”的对象。 注意属性函数的前两个参数名为fset和fget。 利用我们为自己的getter和setter选择这些名称的事实,并使用应用于该作用域的所有本地定义的**(双星号)创建一个关键字字典,将参数传递给属性函数
我们可以创建一个描述符对象。
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