如何在Python中声明常量?

在Java中,我们做:

public static final String CONST_NAME = "Name";

当前回答

我知道这是一个老问题,但由于新的解决方案仍在添加,我想使可能的解决方案列表更加完整。你可以通过从类中继承属性来实现实例中的常量,如下所示:

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式的,你不应该在家里这样做!

然而,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'"

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

e.g

Days_in_week = 7

该值是可变的,即您可以更改它。但既然名字的规则告诉你是常数,你为什么要这么做呢?我是说,这毕竟是你的项目!

这是python中采用的方法。出于同样的原因,没有private关键字。用下划线作为名称的前缀,您就知道它是私有的。代码可以打破规则....就像程序员可以删除private关键字一样。

Python可以添加一个const关键字…但是程序员可以删除关键字,然后如果他们想要更改常量,但为什么要这样做呢?如果你想打破规则,你可以改变规则。但如果名字的意思已经很清楚了,为什么还要费心打破规则呢?

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

全大写的名称告诉您它是一个常量。这才是重要的。没有一种语言强制约束代码,无论如何你都可以修改。

这就是python的哲学。

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


为了让程序员知道变量是常量,通常用大写:

CONST_NAME = "Name"

要在常量发生变化时引发异常,请参阅Alex Martelli的《Python中的常量》。注意,这在实践中并不常用。


从Python 3.8开始,有一个类型。最后一个变量注释,它将告诉静态类型检查器(如myypy)您的变量不应该被重新分配。这是最接近于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

所有给出的答案基本上有两种类型:

创建一些你可以实现的对象 创建一旦定义就不能更改的属性。 使用约定(比如常量全部大写,或者对于Python 3.8,使用最后一个限定符来表示一个或多个名称是常量。

他们可以总结为“你不能用Python做你想做的事情”。

然而,实际上有一种方法可以创建具有真正常量的模块。这样做的代码相当复杂,我将只给出需要做什么,因为它在开源许可下已经可用。

使用导入钩子来创建自定义模块。这里可以找到我为此使用的通用代码。 创建一个特殊的字典,允许只添加一次符合您所选模式的项(例如,名称全部大写),并防止此类名称的值被更改。为此,你需要定义自己的方法,如__setitem__, __delitem__等。这种字典的代码(比如在这个文件中找到的,超过250行)大约有100行长。 普通Python模块的dict不能被修改。因此,在创建模块时,您需要首先执行特殊字典中的代码,然后使用其内容更新模块的字典。 为了防止从模块外部修改常量的值(即monkeypatching),您可以用重新定义的__setattr__和__delattr__方法将模块的__class__替换为自定义的__class__。

关于这个示例的文档可以在这里找到。它可能应该更新,以反映这个问题的答案的数量。

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

只定义空类。

e.g:

class RED: 
    pass
class BLUE: 
    pass