如何在Python中声明常量?
在Java中,我们做:
public static final String CONST_NAME = "Name";
如何在Python中声明常量?
在Java中,我们做:
public static final String CONST_NAME = "Name";
当前回答
我们可以创建一个描述符对象。
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
其他回答
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完全弃用。
我们可以创建一个描述符对象。
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
在其他语言中没有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 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
在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)