ChatGPT解决这个技术问题 Extra ChatGPT

如何在 Python 中创建常量?

如何在 Python 中声明一个常量?

在 Java 中,我们这样做:

public static final String CONST_NAME = "Name";
实际上,使 只读 变量的方法可以通过 python 的 property 函数/装饰器来实现。 invanswer 是自定义用法的一个示例。属性比这更通用,不过,Shalabh Chaturvedi 的 Python Attributes and Methods 对它的工作原理进行了很好的分析。
恕我直言,执行恒常性是“不是pythonic”。在 Python 2.7 中,您甚至可以编写 True=False,然后 (2+2==4)==True 返回 False
正如其他答案所暗示的那样,没有办法或不需要声明常量。但是您可以阅读此PEP关于约定的内容。例如 THIS_IS_A_CONSTANT
@osa:你不能在 python 3 - SyntaxError: can't assign to keyword 中做到这一点。这似乎是一件好事。
令人惊讶的是,直到现在才提到这一点,但 Enums 似乎是定义枚举常量的好方法。

N
Neil

您不能在 Python 中将变量或值声明为常量。

为了向程序员表明一个变量是一个常量,人们通常把它写成大写:

CONST_NAME = "Name"

要在常量更改时引发异常,请参阅 Alex Martelli 的 Constants in 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

在 emacs 中,mypy 没有为 :Final 重新分配提供任何符号。我应该为它进行任何配置设置吗?
给初学者的建议:看看为什么不变性是一种代码质量机制。对于认为 Python 中缺少常量没有问题的高级程序员,也可以这样做。
使用 javascript 一段时间并寻找基于枚举的方法,我发现 yoni 的回答在下面很有帮助stackoverflow.com/a/59935007
m
martineau

没有其他语言中的 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

代码说明:

定义一个接受表达式的函数常量,并使用它来构造一个“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 - 名称由我选择)。然后使用内置的属性函数构造一个可以“设置”或“获取”的对象。注意属性函数的前两个参数名为 fset 和 fget。使用我们为自己的 getter 和 setter 选择这些名称的事实,并使用应用于该范围的所有本地定义的 **(双星号)创建一个关键字字典,以将参数传递给属性函数


i
informatik01

在 Python 中,人们使用命名约定而不是强制执行某些东西,例如 __method 用于 private 方法,使用 _method 用于 protected 方法。

因此,您可以以同样的方式简单地将常量声明为全部大写,例如:

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

使用 def MY_CONSTANT(): return "one" 不会阻止重新分配方法引用,对吗?这不正是鸭子打字的工作原理吗?
“如果你希望这个常量永远不会改变”——这是一个常量的默认属性😬
J
Jon Betts

我最近发现了一个非常简洁的更新,它会自动引发有意义的错误消息并阻止通过 __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 对象。有上层阶级,班名也显得有些俗套,但我觉得整体还是挺简洁的。


S
Saeed Baig

Python 没有常量。

也许最简单的选择是为它定义一个函数:

def MY_CONSTANT():
    return 42

MY_CONSTANT() 现在具有常量的所有功能(加上一些烦人的大括号)。


我只是想添加这个建议,但幸运的是我向下滚动到了低评价的答案。我希望它会得到进一步的支持,并且我完全同意它具有常量的所有功能,并且非常简单明了。查看所有复杂解决方案中的样板代码量,我发现大括号相对不烦人。
这是最简单的答案,尽管应该注意它有一些开销并且不会阻止白痴修改返回值。它只会阻止代码进一步更改源代码
@MrMesees 修改返回值?你的意思是编辑源?但是,即使在 C++ 中,您也不会受到保护,其中常量(如 constexpr)是真正的硬常量。
@Ruslan 我的意思是,由于 python 没有 constexpr,因此在将值返回到外部上下文后,它不会停止正在编辑的值。在此示例中,没有对 42 执行任何操作来强制执行冻结状态。
您可以在 python 中重新声明函数,所以这实际上并不能解决任何问题
F
Fabio says Reinstate Monica

属性是创建常量的一种方式。您可以通过声明一个 getter 属性来做到这一点,但忽略 setter。例如:

class MyFinalProperty(object):

    @property
    def name(self):
        return "John"

您可以查看 an article I've written 以找到更多使用 Python 属性的方法。


被低估的解决方案。我在找到这个页面(不是这个答案)之后才实现了这个,如果还没有的话,我会转回来添加它。我想强调这个答案的有用性。
绝对是一个被低估的解决方案。我正在扫描整个答案列表,想知道为什么没有人考虑这个简单的解决方案。在我看来,唯一合理的解决方案是:它是如此简单方便。然而,所有答案都表明,在 Python 中没有常量是一个糟糕的设计决策。
h
hans_meine

除了两个最重要的答案(只使用带有大写名称的变量,或者使用属性使值只读),我想提一下,可以使用元类来实现 named 常量.我在 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,但仍然非常易于使用和方便。 (该模块还有更多功能,包括只读常量,请参阅其自述文件。)

在各种存储库中都有类似的解决方案,但据我所知,它们要么缺乏我期望从常量中获得的基本特性之一(比如常量,或者是任意类型),要么它们具有深奥的特性使它们不太普遍适用。但是 YMMV,我将不胜感激。 :-)


C
Community

编辑:为 Python 3 添加了示例代码

注意:this other answer 看起来提供了一个更完整的实现,类似于以下内容(具有更多功能)。

首先,制作一个 metaclass

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

这应该可以防止实例道具被更改。要使用它,请继承:

class MyConst(Const):
    A = 1
    B = 2

现在直接或通过实例访问的道具应该是常量:

MyConst.A
# 1
my_const = MyConst()
my_const.A
# 1

MyConst.A = 'changed'
# TypeError
my_const.A = 'changed'
# TypeError

Here's 上面的一个例子。 Here's Python 3 的另一个示例。


G
Georgy

PEP 591 具有“最终”限定符。强制执行取决于类型检查器。

所以你可以这样做:

MY_CONSTANT: Final = 12407

注意: Final 关键字仅适用于 Python 3.8 版本


Final 类型是通用的,如果您已经在进行类型检查以强制执行此操作,他们可能会对您示例中的无类型用法感到不安。我认为这种用法的正确示例应该是:MY_CONSTANT: Final[int] = 12407
@DaveBirch 根据该 PEP,原始语句应该没问题:“类型检查器应该应用其通常的类型推断机制来确定 ID 的类型(这里可能是 int)。”
C
Community

您可以使用命名元组作为一种解决方法来有效地创建一个常量,该常量的工作方式与 Java 中的静态最终变量(Java“常量”)相同。随着变通方法的进行,它有点优雅。 (更优雅的方法是简单地改进 Python 语言——哪种语言可以让你重新定义 math.pi?——但我离题了。)

(当我写这篇文章时,我意识到这个问题的另一个答案提到了命名元组,但我会在这里继续,因为我将展示一种更接近于你在 Java 中所期望的语法,因为不需要创建一个命名的type as 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

致谢:我以为我发明了 namedtuple 方法,但后来我看到其他人给出了类似的(虽然不太紧凑)的答案。然后我还注意到 What are "named tuples" in Python?,它指出 sys.version_info 现在是一个命名元组,所以也许 Python 标准库很早就提出了这个想法。

请注意,不幸的是(这仍然是 Python),您可以完全删除整个 Foo 分配:

>>> Foo = 'bar'

(捂脸)

但至少我们阻止了 Foo.CONST_NAME 值被更改,这总比没有好。祝你好运。


y
yoni keren
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


请注意,这个问题已经有一个公认的答案。请edit您的答案,以确保它比该问题中已有的其他答案有所改进。
其他答案要么重新实现 set_attr,要么具有代码库中任何位置的意外分配的缺点。没有其他答案提到 Enum,更不用说 Enum 和 str 的混合了。
这是迄今为止最好的答案。真正的常量,没有自定义类,简洁的语法。一些答案使用了足够好的属性,但不适用于所有情况。例如,如果您想获得可以按位计算的 int 值,或者使用属性会出现错误。使用枚举,您只需使用 IntFlag 变体即可使其工作。
C
Community

这是一个“常量”类的实现,它创建具有只读(常量)属性的实例。例如,可以使用 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 's FrozenDict,我用它作为起点。已更改,因此使用语法不是 Nums['ONE'],而是 Nums.ONE

并感谢@Raufio 的回答,以提供覆盖 __ setattr __ 的想法。

或者对于具有更多功能的实现,请参阅 @Hans_meine 的 named_constants at GitHub


Python 是成年人同意的语言。没有针对此类事情的保护措施。 Nums._d['PI'] = 22 我相信,该语言本身并没有提供任何将事物标记为非可变对象的方法。
D
David Medenjak

元组在技术上是一个常量,因为如果您尝试更改其中一个值,元组将引发错误。如果要声明一个具有一个值的元组,则在其唯一值之后放置一个逗号,如下所示:

my_tuple = (0 """Or any other value""",)

要检查此变量的值,请使用类似于以下内容:

if my_tuple[0] == 0:
    #Code goes here

如果您尝试更改此值,将引发错误。


V
Victor Bazterra

这是我创建的一组习语,旨在改进一些已有的答案。

我知道常量的使用不是pythonic,你不应该在家里这样做!

然而,Python 就是这样一种动态语言!这个论坛展示了如何创建看起来和感觉像常量的构造。这个答案的主要目的是探索语言可以表达什么。

请不要对我太苛刻:-)。

有关更多详细信息,我写了 accompaniment blog about these idioms

在这篇文章中,我将把一个常量变量称为对值的常量引用(不可变或其他)。此外,我说一个变量在引用客户端代码无法更新其值的可变对象时具有冻结值。

常数空间(SpaceConstants)

这个习惯用法创建了一个看起来像常量变量(又名 SpaceConstants)的命名空间。它是 Alex Martelli 对代码片段的修改,以避免使用模块对象。特别是,这个修改使用了我称之为类工厂的东西,因为在 SpaceConstants 函数中,定义了一个名为 SpaceConstants 的类,并返回了它的一个实例。

我在 stackoverflowblogpost 中探索了使用类工厂在 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 的修改,其中引用的可变对象被冻结。这个实现利用了我在 setattrgetattr 函数之间称为 shared closure 的东西。可变对象的值由函数共享闭包内的变量 cache 复制和引用。它形成了我所说的closure protected copy of a mutable object

使用此习惯用法时必须小心,因为 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 中的非常简单的答案与 class factory 的组合。

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 类的 protected property by closure 从先前的模式派生而来的。

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'"

R
Raufio

我将创建一个覆盖基对象类的 __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”的方式)。


+1 有趣的方法。虽然不如已经提供的答案那么干净。即使是最简单的早期建议解决方案 def ONE(): return 1 也比此答案 ONE.value 更易于使用 ONE()
T
Tomasz Cz.

不幸的是,Python 还没有常量,这很可惜。 ES6 已经向 JavaScript (https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Statements/const) 添加了支持常量,因为它在任何编程语言中都是非常有用的东西。正如 Python 社区的其他答案中所回答的那样,使用约定 - 用户大写变量作为常量,但它不能防止代码中的任意错误。如果你愿意,你可能会发现一个有用的单文件解决方案作为下一个(请参阅文档字符串如何使用它)。

文件常量.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

您可以通过自动将字典转换为命名元组来稍微改进这一点
M
MVP

我们可以创建一个描述符对象。

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

对此表示赞成,因为这个答案实际上解决了原始问题的“静态”部分,并提供了一种使用元类声明基于类的常量的简洁方法,而不是像其他答案中的实例级常量。对我来说更有意义。
K
K4liber

我使用这样的冻结数据类声明常量值:

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'

X
Xavier Ho

声明“常量”的 Pythonic 方式基本上是一个模块级变量:

RED = 1
GREEN = 2
BLUE = 3

然后编写你的类或函数。由于常量几乎总是整数,并且它们在 Python 中也是不可变的,所以你几乎没有机会改变它。

当然,除非您明确设置 RED = 2


是的,但是阻止“显式设置 RED = 2”的能力是能够将变量名声明为“常量”的全部好处(在其他语言中)!
你会从阻止它中得到好处吗? const 最有用的东西通常是编译器优化,这在 Python 中并不是真正的东西。想要一些东西保持不变?只是不要改变它。如果您担心其他人更改它,您可以将其置于他们的范围之外,或者只是意识到,如果有人正在更改它,那是他们的问题,他们需要处理它,而不是您。
@Kevin:“你会得到什么好处......”,static 的好处是为一个类的所有实例的值提供一个单一的存储空间?除非确实有可能声明一个静态/类变量。
根本问题是,有些人可能将其视为真理来源的值,无法更改,并在整个代码中将其用作真理来源,而不是引入魔术值(我在 Python 中看到很多) - 其他人可能会将其视为可以随意更改的东西。当有人更改了一个全局变量,而你不知道它是从哪里更改的,并且应用程序因为 RED="blue" 而不是 "red" 而崩溃时,您正在引入一个完全不必要的问题,而这个问题已经如此简单地解决了被普遍理解。
“阻止它对你有好处吗?”这是不回答语言怪异的pythonic方式:你应该永远不会犯错误,那么为什么要添加约束呢?为什么要像其他语言一样在块周围添加括号?您只需要永远不要错误地删除选项卡。这就是优秀的程序员所做的。如果你不能,那么你就不是一个好的程序员,你应该使用Java。拥有常量的一个明显优势是您的代码检查器将能够在您尝试为常量分配值时告诉您(代码检查器被糟糕的程序员使用)。
J
Juan Ignacio Sánchez

使用 namedtuple 有一种更简洁的方法:

from collections import namedtuple


def make_consts(name, **kwargs):
    return namedtuple(name, kwargs.keys())(**kwargs)

使用示例

CONSTS = make_consts("baz1",
                     foo=1,
                     bar=2)

通过这种精确的方法,您可以命名您的常量。


对于正在阅读本文的每个人,请记住,如果您将可变对象设置为这些常量之一,任何人都可以更改其内部值。比如让 bar=[1, 2, 3],那么,你可以这样做:CONSTS.bar[1] = 'a' 并且它不会被拒绝。所以要小心这个。
我推荐使用 Python 的属性装饰器,而不是我只是为了好玩而制作的这种 hacky 方法。
L
Lym Zoy

如果您想要常量并且不关心它们的值,这里有一个技巧:

只需定义空类。

例如:

class RED: 
    pass
class BLUE: 
    pass

T
TAbdiukov

我正在尝试不同的方法在 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']

python2python3 的兼容性

Simple container for constants

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")


n
n8boyd

Python 字典是可变的,因此它们似乎不是声明常量的好方法:

>>> constants = {"foo":1, "bar":2}
>>> print constants
{'foo': 1, 'bar': 2}
>>> constants["bar"] = 3
>>> print constants
{'foo': 1, 'bar': 3}

i
innov8

在python中,常量只是一个名称全部大写的变量,单词之间用下划线分隔,

例如

DAYS_IN_WEEK = 7

该值是可变的,因为您可以更改它。但是鉴于规则的名称告诉你是一个常数,你为什么要这样做?我的意思是,它毕竟是你的程序!

这是在整个 python 中采用的方法。出于同样的原因,没有 private 关键字。在名称前加上下划线,您就知道它是私有的。代码可以打破规则......就像程序员无论如何都可以删除 private 关键字一样。

Python 可以添加一个 const 关键字...但是程序员可以删除关键字,然后根据需要更改常量,但为什么要这样做?如果你想打破规则,你无论如何都可以改变规则。但是,如果名称清楚地表明意图,为什么还要打破规则呢?

也许有一些单元测试对值进行更改是有意义的?看看一周 8 天会发生什么,即使在现实世界中一周中的天数无法更改。如果语言阻止了您的例外,如果只有这种情况,您需要打破规则......然后您将不得不停止将其声明为常量,即使它在应用程序中仍然是常量,并且有只是这一个测试用例,看看如果它被改变会发生什么。

全部大写的名称告诉您它是一个常量。这才是重要的。不是一种强制限制代码的语言,无论如何您都有权更改。

这就是python的哲学。


J
John

没有完美的方法可以做到这一点。据我了解,大多数程序员只会将标识符大写,因此 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就是这样。

我的建议是避免所有麻烦,只需大写您的标识符。它实际上不是一个适当的常数,但又什么都不会。


C
Community

(本段是对那些提到 namedtuple 的答案 herethere 的评论,但它太长了,无法放入评论中,所以,就这样吧。)

上面提到的 namedtuple 方法绝对是创新的。但是,为了完整起见,在 its official documentation 的 NamedTuple 部分的末尾,它显示:

枚举常量可以用命名元组来实现,但使用简单的类声明更简单、更有效:class Status: open, pending, closed = range(3)

换句话说,官方文档更喜欢使用实用的方式,而不是实际实现只读行为。我想它会成为 Zen of Python 的又一个例子:

简单胜于复杂。实用胜过纯洁。


s
sim

也许 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.


N
Nqobizwe

您可以使用 StringVar 或 IntVar 等,您的常量是 const_val

val = 'Stackoverflow'
const_val = StringVar(val)
const.trace('w', reverse)

def reverse(*args):
    const_val.set(val)

m
mskfisher

您可以使用 collections.namedtupleitertools 做到这一点:

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

佚名

在 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;