如何在Python中创建常量?

1366

在Python中如何声明常量?

在Java中,我们这样做:

public static final String CONST_NAME = "Name";

11
在Python中,通过使用property函数/装饰器,可以实现将变量设置为只读。inv答案是它的一个自定义用例示例。虽然property函数更具有通用性,但关于它的工作原理的良好分析可在Shalabh Chaturvedi的Python Attributes and Methods网站上找到。 - n611x007
35
在我看来,强制使用常量 "不符合 Python 的风格"。在 Python 2.7 中,甚至可以编写 True=False,然后 (2+2==4)==True 将返回 False - Sergey Orshanskiy
8
正如其他答案所建议的那样,没有必要或者说没有办法去声明常量。但是你可以阅读这个PEP来了解惯例。例如:THIS_IS_A_CONSTANT。 - Govinnage Rasika Perera
51
@osa: 在 Python 3 中你不能这样做 - SyntaxError: can't assign to keyword。这看起来是一件好事。 - naught101
10
直到现在都没有提到,但是枚举似乎是定义枚举常量的一种好方式。 - cs95
显示剩余2条评论
44个回答

2

在Python中,不存在常量这一概念,但你可以通过在变量名前添加CONST_并在注释中说明其为常量来表示该变量不可更改:

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;

2
在Python中创建常量的更好方法是从优秀的attrs库中汲取灵感,该库帮助Python程序员创建没有样板文件的类。short-con包通过提供一个方便的包装器来实现相同的常量功能attr.make_class。[免责声明:我是short-con的作者。]
可以通过dictkwargs显式地声明值:这些示例执行相同的操作。constants()函数支持库的所有功能,而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))

名称为基础的用法支持一些风格约定:大写或小写属性名称,以及枚举样式的值。
与内置的enum库创建的常量不同,底层值是可以直接访问的。
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)

1
我为Python常量编写了一个实用库: kkconst - pypi 支持str、int、float和datetime类型。
常量字段的实例将保持其基本类型的行为。
例如:
from __future__ import print_function
from kkconst import (
    BaseConst,
    ConstFloatField,
)

class MathConst(BaseConst):
    PI = ConstFloatField(3.1415926, verbose_name=u"Pi")
    E = ConstFloatField(2.7182818284, verbose_name=u"mathematical constant")  # Euler's number"
    GOLDEN_RATIO = ConstFloatField(0.6180339887, verbose_name=u"Golden Ratio")

magic_num = MathConst.GOLDEN_RATIO
assert isinstance(magic_num, ConstFloatField)
assert isinstance(magic_num, float)

print(magic_num)  # 0.6180339887
print(magic_num.verbose_name)  # Golden Ratio

更多详细用法可以阅读pypi网址: pypi github


1

这并不是完全恒定的,但从Python 3.7开始,您可以使用dataclasses模块,如下所示:

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'

1
你可以将常量包装在一个numpy数组中,标记为只写,并始终通过索引零调用它。
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 发生变化,那么在脚本中第一次调用 CONSTANT[0] 时会很快抛出 TypeError 异常。

尽管...我想如果您在某个时候将其更改为

CONSTANT = [1,2,3]

现在您将不再收到TypeError错误。 嗯......

https://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.setflags.html


1
我知道这是一个老问题,但由于仍有新的解决方案被添加到其中,我想让可能的解决方案列表更加完整。您可以通过继承以下类来通过属性访问在实例内部实现常量:
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())

当继承此类时,您创建的常量将受到保护:

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继承的类的每个实例都有自己的常量),只要它们没有被重新分配,它们就像普通属性一样,编写继承自此的类允许使用与支持常量的语言几乎相同的风格。

此外,如果您想防止任何人通过直接访问instance._constants更改值,则可以使用其他答案中建议的不允许此操作的众多容器之一。最后,如果您真的觉得需要,您可以通过一些更多的AllowConstants属性访问来防止人们将所有instance._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

1
你可以使用元组作为常量变量:
• 元组是一个有序且不可更改的集合。
my_tuple = (1, "Hello", 3.4)
print(my_tuple[0])

1
在我的情况下,我需要不可变的bytearrays来实现一个包含多个字面数字的加密库,我希望确保这些数字是恒定的。 这个答案可以工作,但尝试重新分配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')

一种更强大、简单,甚至更符合"pythonic"方法的是使用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

0

好吧,尽管这已经过时了,让我在这里加上我的意见:-)

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来中断,你可以防止在那里发生任何更新。这样做的一个优点是,你可以在程序中动态添加常量,但一旦一个常量被设置,就无法更改。此外,在设置一个常量之前,你可以添加任何规则或其他要求(比如键必须是字符串、小写字符串或大写字符串等)。

然而,我并没有看到在Python中设置常量的重要性。不像C语言中那样可以进行优化,因此我认为这并非必需的。


0

你可以简单地:

STRING_CONSTANT = "hi"
NUMBER_CONSTANT = 89

希望这能让一切变得更简单。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接